Merge branch 'develop' of https://github.com/frappe/erpnext into show_address_in_online_pos

Resolved Conflicts:erpnext/selling/doctype/customer/customer.json
This commit is contained in:
Saurabh 2018-02-01 02:05:30 +05:30
commit f16d45b0a0
52 changed files with 476 additions and 152 deletions

View File

@ -1,13 +1,6 @@
language: python language: python
dist: trusty dist: trusty
addons:
apt:
sources:
- google-chrome
packages:
- google-chrome-stable
python: python:
- "2.7" - "2.7"
@ -31,15 +24,6 @@ install:
- cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/ - cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/
before_script: before_script:
- wget http://chromedriver.storage.googleapis.com/2.33/chromedriver_linux64.zip
- unzip chromedriver_linux64.zip
- sudo apt-get install libnss3
- sudo apt-get --only-upgrade install google-chrome-stable
- sudo cp chromedriver /usr/local/bin/.
- sudo chmod +x /usr/local/bin/chromedriver
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- sleep 3
- mysql -u root -ptravis -e 'create database test_frappe' - mysql -u root -ptravis -e 'create database test_frappe'
- echo "USE mysql;\nCREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe';\nFLUSH PRIVILEGES;\n" | mysql -u root -ptravis - echo "USE mysql;\nCREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe';\nFLUSH PRIVILEGES;\n" | mysql -u root -ptravis
- echo "USE mysql;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root -ptravis - echo "USE mysql;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root -ptravis
@ -60,24 +44,6 @@ jobs:
- set -e - set -e
- bench run-tests - bench run-tests
env: Server Side Test env: Server Side Test
- # stage
script:
- bench --verbose run-setup-wizard-ui-test
- bench execute erpnext.setup.utils.enable_all_roles_and_domains
- bench run-ui-tests --app erpnext
env: Client Side Test
- # stage
script:
- bench --verbose run-setup-wizard-ui-test
- bench execute erpnext.setup.utils.enable_all_roles_and_domains
- bench run-ui-tests --app erpnext --test-list erpnext/tests/ui/tests2.txt
env: Client Side Test - 2
- # stage
script:
- bench --verbose run-setup-wizard-ui-test
- bench execute erpnext.setup.utils.enable_all_roles_and_domains
- bench run-ui-tests --app erpnext --test-list erpnext/tests/ui/agriculture.txt
env: Agriculture Client Side Test
- # stage - # stage
script: script:
- wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz - wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz

View File

@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
from frappe.utils import getdate from frappe.utils import getdate
__version__ = '10.0.17' __version__ = '10.0.18'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@ -224,12 +224,17 @@ class SalesInvoice(SellingController):
from erpnext.selling.doctype.customer.customer import check_credit_limit from erpnext.selling.doctype.customer.customer import check_credit_limit
validate_against_credit_limit = False validate_against_credit_limit = False
bypass_credit_limit_check_at_sales_order = cint(frappe.db.get_value("Customer", self.customer,
"bypass_credit_limit_check_at_sales_order"))
if bypass_credit_limit_check_at_sales_order:
validate_against_credit_limit = True
for d in self.get("items"): for d in self.get("items"):
if not (d.sales_order or d.delivery_note): if not (d.sales_order or d.delivery_note):
validate_against_credit_limit = True validate_against_credit_limit = True
break break
if validate_against_credit_limit: if validate_against_credit_limit:
check_credit_limit(self.customer, self.company) check_credit_limit(self.customer, self.company, bypass_credit_limit_check_at_sales_order)
def set_missing_values(self, for_validate=False): def set_missing_values(self, for_validate=False):
pos = self.set_pos_fields(for_validate) pos = self.set_pos_fields(for_validate)
@ -242,7 +247,10 @@ class SalesInvoice(SellingController):
super(SalesInvoice, self).set_missing_values(for_validate) super(SalesInvoice, self).set_missing_values(for_validate)
if pos: if pos:
return {"print_format": pos.get("print_format_for_online") } return {
"print_format": pos.get("print_format_for_online"),
"allow_edit_rate": pos.get("allow_user_to_edit_rate")
}
def update_time_sheet(self, sales_invoice): def update_time_sheet(self, sales_invoice):
for d in self.timesheets: for d in self.timesheets:

View File

@ -130,8 +130,8 @@ def get_party_details(party, party_type, args=None):
def get_tax_template(posting_date, args): def get_tax_template(posting_date, args):
"""Get matching tax rule""" """Get matching tax rule"""
args = frappe._dict(args) args = frappe._dict(args)
conditions = ["""(from_date is null or from_date = '' or from_date <= '{0}') conditions = ["""(from_date is null or from_date <= '{0}')
and (to_date is null or to_date = '' or to_date >= '{0}')""".format(posting_date)] and (to_date is null or to_date >= '{0}')""".format(posting_date)]
for key, value in args.iteritems(): for key, value in args.iteritems():
if key=="use_for_shopping_cart": if key=="use_for_shopping_cart":

View File

@ -8,7 +8,7 @@
{ {
"doctype": "Supplier", "doctype": "Supplier",
"supplier_name": "_Test Supplier P", "supplier_name": "_Test Supplier P",
"supplier_type": "_Test Supplier Type", "supplier_type": "_Test Supplier Type"
}, },
{ {
"doctype": "Supplier", "doctype": "Supplier",

View File

@ -257,7 +257,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
def get_batch_no(doctype, txt, searchfield, start, page_len, filters): def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
cond = "" cond = ""
if filters.get("posting_date"): if filters.get("posting_date"):
cond = "and (ifnull(batch.expiry_date, '')='' or batch.expiry_date >= %(posting_date)s)" cond = "and (batch.expiry_date is null or batch.expiry_date >= %(posting_date)s)"
batch_nos = None batch_nos = None
args = { args = {

View File

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -10,4 +10,17 @@ Assessment Criteria is be used when scheduling Assessment Plan for Student Group
<img class="screenshot" alt="Assessment Plan Criteria" src="/docs/assets/img/education/assessment/assessment-plan-criteria.png"> <img class="screenshot" alt="Assessment Plan Criteria" src="/docs/assets/img/education/assessment/assessment-plan-criteria.png">
#### Video Tutorial on Assessment Criteria
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed/t8ZDDq4qtIk?end=52' frameborder='0' allowfullscreen>
</iframe>
</div>
<div>
{next} {next}

View File

@ -10,4 +10,17 @@ On the same lines, you can also define multiple Assessment Group bases on assess
<img class="screenshot" alt="Assessment Group Term" src="/docs/assets/img/education/assessment/assessment-group-details.png"> <img class="screenshot" alt="Assessment Group Term" src="/docs/assets/img/education/assessment/assessment-group-details.png">
#### Video Tutorial on Assessment Group
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed/I1T7Z2JbcP4' frameborder='0' allowfullscreen>
</iframe>
</div>
<div>
{next} {next}

View File

@ -16,4 +16,17 @@ To schedule an assessment/examination for a Student Group, for specific Course,
<img class="screenshot" alt="Assessment Plan Criteria" src="/docs/assets/img/education/assessment/assessment-plan-criteria.png"> <img class="screenshot" alt="Assessment Plan Criteria" src="/docs/assets/img/education/assessment/assessment-plan-criteria.png">
#### Video Tutorial on Assessment Plan
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed/Q9CzzoYb_Js' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -4,4 +4,16 @@ Assessment Result is a log of marks/grades earned by the student for specific As
<img class="screenshot" alt="Assessment Result" src="/docs/assets/img/education/assessment/assessment-result.png"> <img class="screenshot" alt="Assessment Result" src="/docs/assets/img/education/assessment/assessment-result.png">
#### Video Tutorial on Assessment Result
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed/U8ZRB8CM-UM?end=89' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -6,4 +6,17 @@ Assessment Result Tool help you entering marks earned by the Students for specif
As you go on entering marks for a Student, and switch to next student, in the backend, Student Result record will be auto-created for that Student. As you go on entering marks for a Student, and switch to next student, in the backend, Student Result record will be auto-created for that Student.
#### Video Tutorial on Assessment Result Tool
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed/U8ZRB8CM-UM?start=80' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -4,4 +4,17 @@ In the Grading Scale, you can define various grades and threshold for them. Base
<img class="screenshot" alt="Grading Scale" src="/docs/assets/img/education/assessment/grading-scale.png"> <img class="screenshot" alt="Grading Scale" src="/docs/assets/img/education/assessment/grading-scale.png">
#### Video Tutorial on Grading Scale
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed/t8ZDDq4qtIk?start=52' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -1,3 +1,3 @@
student-attendance student-attendance
student-leave-application
student-attendance-tool student-attendance-tool
student-leave-application

View File

@ -12,4 +12,17 @@ Student detials will be autofetched and you can mark the attendance of the given
<img class="screenshot" alt="Student Attendance" src="/docs/assets/img/education/setup/student-attendance-tool.gif"> <img class="screenshot" alt="Student Attendance" src="/docs/assets/img/education/setup/student-attendance-tool.gif">
#### Tutorial Video for Student Attendance Tool
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed//j9pgkPuyiaI?start=63' frameborder='0' allowfullscreen>
</iframe>
</div>
<div>
{next} {next}

View File

@ -12,4 +12,17 @@ Select the **Student, Course Schedule and Student Group** for which attendance i
**Student Attendance tool** can be used for bulk updation of the attendance based on **Batch, Course or Activity**. **Student Attendance tool** can be used for bulk updation of the attendance based on **Batch, Course or Activity**.
#### Tutorial Video on Student Attendance
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed//j9pgkPuyiaI' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -10,4 +10,17 @@ Incase the student is not attending the institute in order to participate or rep
Once a Leave Application is recorded for a student it will not be recorded in the absent student report as he has applied for a leave. Once a Leave Application is recorded for a student it will not be recorded in the absent student report as he has applied for a leave.
#### Tutorial Video for Student Leave Application
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed/NwwH5t-NKBE' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -18,4 +18,16 @@ You can create the Program Enrollment for :
For promoting the students, the new academic year, academic term and program can also be selected for the enrollment of the fetched students list. For promoting the students, the new academic year, academic term and program can also be selected for the enrollment of the fetched students list.
#### Video Tutorial for Program Enrollment Tool
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed//5nxWYBRHY_o?start=82' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -17,5 +17,15 @@ Student Batch: To categorize student into different sections/batches, you can as
Student Category: For the Institutions having multiple Fees Structure, this field can be used to differentiate the student enrollment in a given fee category. Student Category: For the Institutions having multiple Fees Structure, this field can be used to differentiate the student enrollment in a given fee category.
#### Video Tutorial For Program Enrollment
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed//5nxWYBRHY_o?start=0&end=88' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -27,4 +27,16 @@ the system shall create a student against that applicant and redirect you to the
<img class="screenshot" alt="Student Applicant Enrollment" src="/docs/assets/img/education/admission/student-applicant-enroll.png"> <img class="screenshot" alt="Student Applicant Enrollment" src="/docs/assets/img/education/admission/student-applicant-enroll.png">
#### Video Tutorial for Student Application
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed/l8PUACusN3E' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -20,4 +20,16 @@ You can set the "Income Account" and "Receivable Account" in the Accounts sectio
If you are going to use this in the Fee Schedule, you must select the Accounts carefully as Fee Schedule updates the respected Accounts in bulk. If you are going to use this in the Fee Schedule, you must select the Accounts carefully as Fee Schedule updates the respected Accounts in bulk.
#### Video Tutorial for Fee Structure
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed//_ZkvyVnWgYk' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -5,6 +5,18 @@ You can see the overall course schedule in the Calendar view.
<img class="screenshot" alt="Course Schedule" src="/docs/assets/img/education/schedule/course-schedule.png"> <img class="screenshot" alt="Course Schedule" src="/docs/assets/img/education/schedule/course-schedule.png">
#### Video Tutorial on Course Scheduling
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed/iy-DBV9jI-A?start=0&end=114' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
### Marking Attendance ### Marking Attendance
You can mark attendance for a Student Group against a Course Schedule. You can mark attendance for a Student Group against a Course Schedule.

View File

@ -20,4 +20,14 @@ This tool can be used to create 'Course Schedules'.
- Check the 'Reschedule' checkbox and then click 'Schedule Course' button. - Check the 'Reschedule' checkbox and then click 'Schedule Course' button.
- System will delete existing Course Schedules for specified Course within the mentioned Course Start Date and Course End Date and crate new Course Schedules. - System will delete existing Course Schedules for specified Course within the mentioned Course Start Date and Course End Date and crate new Course Schedules.
#### Video Tutorial on Scheduling Tool
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed/iy-DBV9jI-A?start=114' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -12,4 +12,18 @@ Once a **Course** is created, a course schedule can defined for the same.
The Course form is further linked to **Program, Student Group and Assessment Plan** doctypes. The links allow to view/create the related documents for a **Course**. The Course form is further linked to **Program, Student Group and Assessment Plan** doctypes. The links allow to view/create the related documents for a **Course**.
#### Video Tutorial for Course
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed//1ueE4seFTp8?start=66' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -16,5 +16,16 @@ While creating the **Assessment Plan** for a Student Group, **Instructor** can b
Further, the log for the Instructor can be entered in the Instructor Log table which can be used for keeping the records of subjects taught by that Instructor. Further, the log for the Instructor can be entered in the Instructor Log table which can be used for keeping the records of subjects taught by that Instructor.
#### Video Tutorial for Instructor
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed//rVqQYS1_02k' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -4,7 +4,7 @@ An educational program is a program written by the institutions which determines
To create a Program go to : To create a Program go to :
###education >> Setup >> Program >> New Program > Education > Setup > Program > New Program
Enter a unique code for every **Program**. You can also link the **Program** to the department under which it is conducted. Enter a unique code for every **Program**. You can also link the **Program** to the department under which it is conducted.
@ -16,4 +16,16 @@ Add the relevant Course and the Fee details for a program.
The Program Doctype is further linked to the **Student applicant**, **Program enrollment, Student group, Fee structre and Fee**. The links allow to view or create the related document for a Program. The Program Doctype is further linked to the **Student applicant**, **Program enrollment, Student group, Fee structre and Fee**. The links allow to view or create the related document for a Program.
#### Video Tutorial on Program and Courses
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed//1ueE4seFTp8?end=70' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -2,7 +2,7 @@
The Education Settings page allow you to setup basic settings like **Academic Year and Term** for the educational setup. The Education Settings page allow you to setup basic settings like **Academic Year and Term** for the educational setup.
<img class="screenshot" alt="Student" src="/docs/assets/img/education/student/education.png"> <img class="screenshot" alt="Student" src="/docs/assets/img/education/student/schools-settings.png">
The checkbox to Validate Batch for Students in Student Group enables the Student Batch validation for every Student from the Program Enrollment for the **Batch** based on **Student Group** The checkbox to Validate Batch for Students in Student Group enables the Student Batch validation for every Student from the Program Enrollment for the **Batch** based on **Student Group**

View File

@ -4,7 +4,7 @@ The Student group creation tool allows you to create student groups in bulk.
To create Student group using this tool go to To create Student group using this tool go to
##education >>Student >> Student Group creation tool > Education > Student > Student Group creation tool
Select the **Academic Term** and the **Program** for which a student group is to be created. Select the **Academic Term** and the **Program** for which a student group is to be created.
@ -14,4 +14,16 @@ By default the student group is created based on the **Course** only. The check
You can leave it unchecked if you don't want to consider batch while making course based groups. You can leave it unchecked if you don't want to consider batch while making course based groups.
#### Tutorial Video on Student Group Creation Tool
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed/5K_smeeE1Q4?start=108' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -6,9 +6,9 @@ A Student Group needs to be created for every course for **Academic Term** and *
To create a Student Group go to: To create a Student Group go to:
education >> Student >> New Student Group > Education > Student > New Student Group
<img class="screenshot" alt="Student Group" src="/docs/assets/img/education/student/Student-group.gif"> <img class="screenshot" alt="Student Group" src="/docs/assets/img/education/student/student-group.gif">
To create a Student group based on **Batch**, select the **Progam** and **Batch**, where as to create a Student group based on **Course**, you will only have to select the Course Code. Creating a student group based on activity allows you to group of student for events and activities happening in the institute. To create a Student group based on **Batch**, select the **Progam** and **Batch**, where as to create a Student group based on **Course**, you will only have to select the Course Code. Creating a student group based on activity allows you to group of student for events and activities happening in the institute.
@ -18,5 +18,18 @@ Once a student group is created you can mark attendance for the group.
You can also update the **Email Group** for the Student Group. Click on Update Email Group to add all the email ids of the gaurdians in the respective email group and **Newsletter** can be created and sent to the Email group. You can also update the **Email Group** for the Student Group. Click on Update Email Group to add all the email ids of the gaurdians in the respective email group and **Newsletter** can be created and sent to the Email group.
#### Tutorial Video on Student Groups
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed/QILiHiTD3uc'
frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -4,9 +4,21 @@ A Student is a person who has enrolled at your institute and you have accepted t
The Student doctype maintains detials like personal information, date of birth, address etc. It also records the **Guardian** and sibling details. The Student doctype maintains detials like personal information, date of birth, address etc. It also records the **Guardian** and sibling details.
<img class="screenshot" alt="Student" src="/docs/assets/img/education/student/student.png"> <img class="screenshot" alt="Student" src="/docs/assets/img/education/student/student.png">
The student is enrolled in a **Program** when the application is approved. Once the enrollement is done the **Student Applicant** status is update to Admitted. The student is enrolled in a **Program** when the application is approved. Once the enrollement is done the **Student Applicant** status is update to Admitted.
You can view every doctype created for a particular student. Eg : Fees, Student Group, etc You can view every doctype created for a particular student. Eg : Fees, Student Group, etc
#### Video Tutorial on Student Management
<div>
<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
</style>
<div class='embed-container'>
<iframe src='https://www.youtube.com/embed//nIUsbl0rEE0' frameborder='0' allowfullscreen>
</iframe>
</div>
</div>
{next} {next}

View File

@ -37,7 +37,6 @@ def invite_guardian(guardian):
frappe.throw(_("Please set Email Address")) frappe.throw(_("Please set Email Address"))
else: else:
guardian_as_user = frappe.get_value('User', dict(email=guardian_doc.email_address)) guardian_as_user = frappe.get_value('User', dict(email=guardian_doc.email_address))
print guardian_as_user
if guardian_as_user: if guardian_as_user:
frappe.msgprint(_("User {0} already exists").format(getlink("User", guardian_as_user))) frappe.msgprint(_("User {0} already exists").format(getlink("User", guardian_as_user)))
return guardian_as_user return guardian_as_user

View File

@ -9,60 +9,60 @@ from frappe.core.doctype.sms_settings.sms_settings import send_sms
import json import json
class HealthcareSettings(Document): class HealthcareSettings(Document):
def validate(self): def validate(self):
for key in ["collect_registration_fee","manage_customer","patient_master_name", for key in ["collect_registration_fee","manage_customer","patient_master_name",
"require_test_result_approval","require_sample_collection", "default_medical_code_standard"]: "require_test_result_approval","require_sample_collection", "default_medical_code_standard"]:
frappe.db.set_default(key, self.get(key, "")) frappe.db.set_default(key, self.get(key, ""))
if(self.collect_registration_fee): if(self.collect_registration_fee):
if self.registration_fee <= 0 : if self.registration_fee <= 0 :
frappe.throw("Registration fee can not be Zero") frappe.throw("Registration fee can not be Zero")
@frappe.whitelist() @frappe.whitelist()
def get_sms_text(doc): def get_sms_text(doc):
sms_text = {} sms_text = {}
doc = frappe.get_doc("Lab Test",doc) doc = frappe.get_doc("Lab Test",doc)
#doc = json.loads(doc) #doc = json.loads(doc)
context = {"doc": doc, "alert": doc, "comments": None} context = {"doc": doc, "alert": doc, "comments": None}
emailed = frappe.db.get_value("Healthcare Settings", None, "sms_emailed") emailed = frappe.db.get_value("Healthcare Settings", None, "sms_emailed")
sms_text['emailed'] = frappe.render_template(emailed, context) sms_text['emailed'] = frappe.render_template(emailed, context)
printed = frappe.db.get_value("Healthcare Settings", None, "sms_printed") printed = frappe.db.get_value("Healthcare Settings", None, "sms_printed")
sms_text['printed'] = frappe.render_template(printed, context) sms_text['printed'] = frappe.render_template(printed, context)
return sms_text return sms_text
def send_registration_sms(doc): def send_registration_sms(doc):
if (frappe.db.get_value("Healthcare Settings", None, "reg_sms")=='1'): if (frappe.db.get_value("Healthcare Settings", None, "reg_sms")=='1'):
if doc.mobile: if doc.mobile:
context = {"doc": doc, "alert": doc, "comments": None} context = {"doc": doc, "alert": doc, "comments": None}
if doc.get("_comments"): if doc.get("_comments"):
context["comments"] = json.loads(doc.get("_comments")) context["comments"] = json.loads(doc.get("_comments"))
messages = frappe.db.get_value("Healthcare Settings", None, "reg_msg") messages = frappe.db.get_value("Healthcare Settings", None, "reg_msg")
messages = frappe.render_template(messages, context) messages = frappe.render_template(messages, context)
number = [doc.mobile] number = [doc.mobile]
send_sms(number,messages) send_sms(number,messages)
else: else:
frappe.msgprint(doc.name + " Has no mobile number to send registration SMS", alert=True) frappe.msgprint(doc.name + " Has no mobile number to send registration SMS", alert=True)
def get_receivable_account(company): def get_receivable_account(company):
receivable_account = get_account(None, "receivable_account", "Healthcare Settings", company) receivable_account = get_account(None, "receivable_account", "Healthcare Settings", company)
if receivable_account: if receivable_account:
return receivable_account return receivable_account
return frappe.db.get_value("Company", company, "default_receivable_account") return frappe.db.get_value("Company", company, "default_receivable_account")
def get_income_account(physician, company): def get_income_account(physician, company):
if(physician): if(physician):
income_account = get_account("Physician", None, physician, company) income_account = get_account("Physician", None, physician, company)
if income_account: if income_account:
return income_account return income_account
income_account = get_account(None, "income_account", "Healthcare Settings", company) income_account = get_account(None, "income_account", "Healthcare Settings", company)
if income_account: if income_account:
return income_account return income_account
return frappe.db.get_value("Company", company, "default_income_account") return frappe.db.get_value("Company", company, "default_income_account")
def get_account(parent_type, parent_field, parent, company): def get_account(parent_type, parent_field, parent, company):
if(parent_type): if(parent_type):
return frappe.db.get_value("Party Account", return frappe.db.get_value("Party Account",
{"parenttype": parent_type, "parent": parent, "company": company}, "account") {"parenttype": parent_type, "parent": parent, "company": company}, "account")
if(parent_field): if(parent_field):
return frappe.db.get_value("Party Account", return frappe.db.get_value("Party Account",
{"parentfield": parent_field, "parent": parent, "company": company}, "account") {"parentfield": parent_field, "parent": parent, "company": company}, "account")

View File

@ -592,7 +592,7 @@ def validate_bom_no(item, bom_no):
@frappe.whitelist() @frappe.whitelist()
def get_children(doctype, parent=None, is_root=False, **filters): def get_children(doctype, parent=None, is_root=False, **filters):
if not parent: if not parent or parent=="BOM":
frappe.msgprint(_('Please select a BOM')) frappe.msgprint(_('Please select a BOM'))
return return

View File

@ -11,7 +11,8 @@ frappe.treeview_settings["BOM"] = {
title: "BOM", title: "BOM",
breadcrumb: "Manufacturing", breadcrumb: "Manufacturing",
disable_add_node: true, disable_add_node: true,
root_label: "All Bill of Materials", //fieldname from filters root_label: "BOM", //fieldname from filters
get_tree_root: false,
get_label: function(node) { get_label: function(node) {
if(node.data.qty) { if(node.data.qty) {
return node.data.qty + " x " + node.data.item_code; return node.data.qty + " x " + node.data.item_code;
@ -19,6 +20,23 @@ frappe.treeview_settings["BOM"] = {
return node.data.item_code || node.data.value; return node.data.item_code || node.data.value;
} }
}, },
onload: function(me) {
var label = frappe.get_route()[0] + "/" + frappe.get_route()[1];
if(frappe.pages[label]) {
delete frappe.pages[label];
}
var filter = me.opts.filters[0];
if(frappe.route_options && frappe.route_options[filter.fieldname]) {
var val = frappe.route_options[filter.fieldname];
delete frappe.route_options[filter.fieldname];
filter.default = "";
me.args[filter.fieldname] = val;
me.root_label = val;
me.page.set_title(val);
}
me.make_tree();
},
toolbar: [ toolbar: [
{ toggle_btn: true }, { toggle_btn: true },
{ {

View File

@ -490,7 +490,7 @@ class ProductionOrder(Document):
and detail.parent = entry.name and detail.parent = entry.name
and detail.item_code = %s''', (self.name, d.item_code))[0][0] and detail.item_code = %s''', (self.name, d.item_code))[0][0]
d.db_set('transferred_qty', transferred_qty, update_modified = False) d.db_set('transferred_qty', flt(transferred_qty), update_modified = False)
@frappe.whitelist() @frappe.whitelist()

View File

@ -345,7 +345,7 @@ erpnext.patches.v7_0.repost_bin_qty_and_item_projected_qty
erpnext.patches.v7_1.set_prefered_contact_email erpnext.patches.v7_1.set_prefered_contact_email
execute:frappe.reload_doc('accounts', 'doctype', 'accounts_settings') execute:frappe.reload_doc('accounts', 'doctype', 'accounts_settings')
execute:frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancellation_of_invoice", 0) execute:frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancellation_of_invoice", 0)
execute:frappe.db.sql("update `tabStock Entry` set total_amount = null where purpose in('Repack', 'Manufacture')") execute:frappe.db.sql("update `tabStock Entry` set total_amount = 0 where purpose in('Repack', 'Manufacture')")
erpnext.patches.v7_1.save_stock_settings erpnext.patches.v7_1.save_stock_settings
erpnext.patches.v7_0.repost_gle_for_pi_with_update_stock #2016-11-01 erpnext.patches.v7_0.repost_gle_for_pi_with_update_stock #2016-11-01
erpnext.patches.v7_1.add_account_user_role_for_timesheet erpnext.patches.v7_1.add_account_user_role_for_timesheet

View File

@ -20,7 +20,10 @@ def update_po_per_received_per_billed():
where parent = `tabPurchase Order`.name), 2), where parent = `tabPurchase Order`.name), 2),
`tabPurchase Order`.per_billed = ifnull(round((select sum( if(amount > ifnull(billed_amt, 0), `tabPurchase Order`.per_billed = ifnull(round((select sum( if(amount > ifnull(billed_amt, 0),
ifnull(billed_amt, 0), amount)) / sum(amount) *100 from `tabPurchase Order Item` ifnull(billed_amt, 0), amount)) / sum(amount) *100 from `tabPurchase Order Item`
where parent = `tabPurchase Order`.name), 2), 0)""") where parent = `tabPurchase Order`.name), 2), 0)
where
net_total > 0
""")
def update_so_per_delivered_per_billed(): def update_so_per_delivered_per_billed():
frappe.db.sql(""" frappe.db.sql("""
@ -32,7 +35,10 @@ def update_so_per_delivered_per_billed():
where parent = `tabSales Order`.name), 2), where parent = `tabSales Order`.name), 2),
`tabSales Order`.per_billed = ifnull(round((select sum( if(amount > ifnull(billed_amt, 0), `tabSales Order`.per_billed = ifnull(round((select sum( if(amount > ifnull(billed_amt, 0),
ifnull(billed_amt, 0), amount)) / sum(amount) *100 from `tabSales Order Item` ifnull(billed_amt, 0), amount)) / sum(amount) *100 from `tabSales Order Item`
where parent = `tabSales Order`.name), 2), 0)""") where parent = `tabSales Order`.name), 2), 0)
where
net_total > 0
""")
def update_status(): def update_status():
frappe.db.sql(""" frappe.db.sql("""

View File

@ -5,5 +5,5 @@ def execute():
frappe.db.sql(""" frappe.db.sql("""
update `tabCurrency Exchange` update `tabCurrency Exchange`
set `date` = '2010-01-01' set `date` = '2010-01-01'
where date is null or date = '' or date = '0000-00-00' where date is null or date = '0000-00-00'
""") """)

View File

@ -7,7 +7,7 @@ def execute():
frappe.reload_doc("hr", "doctype", "attendance") frappe.reload_doc("hr", "doctype", "attendance")
frappe.db.sql("""update `tabAttendance` frappe.db.sql("""update `tabAttendance`
set attendance_date = att_date set attendance_date = att_date
where attendance_date is null or attendance_date = '' or attendance_date = '0000-00-00'""") where attendance_date is null or attendance_date = '0000-00-00'""")
update_reports("Attendance", "att_date", "attendance_date") update_reports("Attendance", "att_date", "attendance_date")
update_users_report_view_settings("Attendance", "att_date", "attendance_date") update_users_report_view_settings("Attendance", "att_date", "attendance_date")

View File

@ -9,8 +9,7 @@ def execute():
salary_slips = frappe.db.sql("""select month, name, fiscal_year from `tabSalary Slip` salary_slips = frappe.db.sql("""select month, name, fiscal_year from `tabSalary Slip`
where (month is not null and month != '') and where (month is not null and month != '') and
(start_date is null or start_date = '') and start_date is null and end_date is null and docstatus != 2""", as_dict=True)
(end_date is null or end_date = '') and docstatus != 2""", as_dict=True)
for salary_slip in salary_slips: for salary_slip in salary_slips:
if not cint(salary_slip.month): if not cint(salary_slip.month):

View File

@ -8,7 +8,7 @@ def execute():
frappe.db.sql(""" frappe.db.sql("""
update `tabSales Order` update `tabSales Order`
set delivery_date = final_delivery_date set delivery_date = final_delivery_date
where (delivery_date is null or delivery_date = '' or delivery_date = '0000-00-00') where (delivery_date is null or delivery_date = '0000-00-00')
and order_type = 'Sales'""") and order_type = 'Sales'""")
frappe.db.sql(""" frappe.db.sql("""
@ -16,8 +16,6 @@ def execute():
set so_item.delivery_date = so.delivery_date set so_item.delivery_date = so.delivery_date
where so.name = so_item.parent where so.name = so_item.parent
and so.order_type = 'Sales' and so.order_type = 'Sales'
and (so_item.delivery_date is null or so_item.delivery_date = '' and (so_item.delivery_date is null or so_item.delivery_date = '0000-00-00')
or so_item.delivery_date = '0000-00-00') and (so.delivery_date is not null and so.delivery_date != '0000-00-00')
and (so.delivery_date is not null and so.delivery_date != ''
and so.delivery_date != '0000-00-00')
""") """)

View File

@ -1066,9 +1066,9 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "", "default": "0",
"fieldname": "payment_terms", "fieldname": "bypass_credit_limit_check_at_sales_order",
"fieldtype": "Link", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
@ -1076,10 +1076,9 @@
"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": "Default Payment Terms Template", "label": "Bypass credit limit check at Sales Order",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Payment Terms Template",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
@ -1098,9 +1097,8 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "0", "fieldname": "column_break_34",
"fieldname": "bypass_credit_limit_check_at_sales_order", "fieldtype": "Column Break",
"fieldtype": "Check",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
@ -1108,7 +1106,6 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Bypass credit limit check at Sales Order",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@ -1123,6 +1120,38 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "payment_terms",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Payment Terms Template",
"length": 0,
"no_copy": 0,
"options": "Payment Terms Template",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,

View File

@ -70,12 +70,12 @@ class Customer(TransactionBase):
def fetch_primary_address_and_contact_detail(self): def fetch_primary_address_and_contact_detail(self):
if(self.customer_primary_contact): if(self.customer_primary_contact):
primary_contact_doc = frappe.get_doc("Contact",self.customer_primary_contact) primary_contact_doc = frappe.get_doc("Contact",self.customer_primary_contact)
self.db_set('mobile_no', primary_contact_doc.mobile_no) self.mobile_no = primary_contact_doc.mobile_no
self.db_set('email_id', primary_contact_doc.email_id) self.email_id = primary_contact_doc.email_id
if(self.customer_primary_address): if(self.customer_primary_address):
primary_address_doc = frappe.get_doc("Address",self.customer_primary_address) primary_address_doc = frappe.get_doc("Address",self.customer_primary_address)
self.db_set('primary_address', "<br>" + primary_address_doc.get_display()) self.primary_address = "<br>" + primary_address_doc.get_display()
def create_primary_contact(self): def create_primary_contact(self):
if not self.customer_primary_contact and not self.lead_name: if not self.customer_primary_contact and not self.lead_name:
@ -197,12 +197,14 @@ def get_customer_list(doctype, txt, searchfield, start, page_len, filters):
("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len)) ("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len))
def check_credit_limit(customer, company): def check_credit_limit(customer, company, ignore_outstanding_sales_order=False, extra_amount=0):
customer_outstanding = get_customer_outstanding(customer, company) customer_outstanding = get_customer_outstanding(customer, company, ignore_outstanding_sales_order)
if extra_amount > 0:
customer_outstanding += flt(extra_amount)
credit_limit = get_credit_limit(customer, company) credit_limit = get_credit_limit(customer, company)
if credit_limit > 0 and flt(customer_outstanding) > credit_limit: if credit_limit > 0 and flt(customer_outstanding) > credit_limit:
msgprint(_("Credit limit has been crossed for customer {0} {1}/{2}") msgprint(_("Credit limit has been crossed for customer {0} ({1}/{2})")
.format(customer, customer_outstanding, credit_limit)) .format(customer, customer_outstanding, credit_limit))
# If not authorized person raise exception # If not authorized person raise exception
@ -243,7 +245,8 @@ def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=F
and dn.customer=%s and dn.company=%s and dn.customer=%s and dn.company=%s
and dn.docstatus = 1 and dn.status not in ('Closed', 'Stopped') and dn.docstatus = 1 and dn.status not in ('Closed', 'Stopped')
and ifnull(dn_item.against_sales_order, '') = '' and ifnull(dn_item.against_sales_order, '') = ''
and ifnull(dn_item.against_sales_invoice, '') = ''""", (customer, company), as_dict=True) and ifnull(dn_item.against_sales_invoice, '') = ''
""", (customer, company), as_dict=True)
outstanding_based_on_dn = 0.0 outstanding_based_on_dn = 0.0

View File

@ -484,11 +484,6 @@ def make_delivery_note(source_name, target_doc=None):
else: else:
target.po_no = source.po_no target.po_no = source.po_no
# Since the credit limit check is bypassed at sales order level,
# we need to check it at delivery note
if cint(frappe.db.get_value("Customer", source.customer, "bypass_credit_limit_check_at_sales_order")):
check_credit_limit(source.customer, source.company)
target.ignore_pricing_rule = 1 target.ignore_pricing_rule = 1
target.run_method("set_missing_values") target.run_method("set_missing_values")
target.run_method("calculate_taxes_and_totals") target.run_method("calculate_taxes_and_totals")
@ -553,10 +548,6 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
target.run_method("set_missing_values") target.run_method("set_missing_values")
target.run_method("calculate_taxes_and_totals") target.run_method("calculate_taxes_and_totals")
# Since the credit limit check is bypassed at sales order level, we need to check it at sales invoice
if cint(frappe.db.get_value("Customer", source.customer, "bypass_credit_limit_check_at_sales_order")):
check_credit_limit(source.customer, source.company)
# set company address # set company address
target.update(get_company_address(target.company)) target.update(get_company_address(target.company))
if target.company_address: if target.company_address:

View File

@ -468,6 +468,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
if (r.message) { if (r.message) {
this.frm.meta.default_print_format = r.message.print_format || 'POS Invoice'; this.frm.meta.default_print_format = r.message.print_format || 'POS Invoice';
this.frm.allow_edit_rate = r.message.allow_edit_rate;
} }
} }
@ -739,6 +740,7 @@ class POSCart {
disable_highlight: ['Qty', 'Disc', 'Rate', 'Pay'], disable_highlight: ['Qty', 'Disc', 'Rate', 'Pay'],
reset_btns: ['Qty', 'Disc', 'Rate', 'Pay'], reset_btns: ['Qty', 'Disc', 'Rate', 'Pay'],
del_btn: 'Del', del_btn: 'Del',
disable_btns: !this.frm.allow_edit_rate ? ['Rate']: [],
wrapper: this.wrapper.find('.number-pad-container'), wrapper: this.wrapper.find('.number-pad-container'),
onclick: (btn_value) => { onclick: (btn_value) => {
// on click // on click
@ -1269,7 +1271,7 @@ class NumberPad {
constructor({ constructor({
wrapper, onclick, button_array, wrapper, onclick, button_array,
add_class={}, disable_highlight=[], add_class={}, disable_highlight=[],
reset_btns=[], del_btn='', reset_btns=[], del_btn='', disable_btns
}) { }) {
this.wrapper = wrapper; this.wrapper = wrapper;
this.onclick = onclick; this.onclick = onclick;
@ -1278,6 +1280,7 @@ class NumberPad {
this.disable_highlight = disable_highlight; this.disable_highlight = disable_highlight;
this.reset_btns = reset_btns; this.reset_btns = reset_btns;
this.del_btn = del_btn; this.del_btn = del_btn;
this.disable_btns = disable_btns;
this.make_dom(); this.make_dom();
this.bind_events(); this.bind_events();
this.value = ''; this.value = '';
@ -1308,6 +1311,14 @@ class NumberPad {
} }
this.set_class(); this.set_class();
this.disable_btns.forEach((btn) => {
const $btn = this.get_btn(btn);
$btn.prop("disabled", true)
$btn.hover(() => {
$btn.css('cursor','not-allowed');
})
})
} }
set_class() { set_class() {

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from operations import install_fixtures, taxes_setup, defaults_setup, company_setup, sample_data from .operations import install_fixtures, taxes_setup, defaults_setup, company_setup, sample_data
def get_setup_stages(args=None): def get_setup_stages(args=None):
if frappe.db.sql("select name from tabCompany"): if frappe.db.sql("select name from tabCompany"):

View File

@ -203,7 +203,7 @@ def set_batch_nos(doc, warehouse_field, throw=False):
else: else:
batch_qty = get_batch_qty(batch_no=d.batch_no, warehouse=warehouse) batch_qty = get_batch_qty(batch_no=d.batch_no, warehouse=warehouse)
if flt(batch_qty, d.precision("qty")) < flt(qty, d.precision("qty")): if flt(batch_qty, d.precision("qty")) < flt(qty, d.precision("qty")):
frappe.throw(_("Row #{0}: The batch {1} has only {2} qty. Please select another batch which has {3} qty available or split the row into multiple rows, to deliver/issue from multiple batches").format(d.idx, d.batch_no, batch_qty, d.stock_qty)) frappe.throw(_("Row #{0}: The batch {1} has only {2} qty. Please select another batch which has {3} qty available or split the row into multiple rows, to deliver/issue from multiple batches").format(d.idx, d.batch_no, batch_qty, qty))
@frappe.whitelist() @frappe.whitelist()

View File

@ -235,13 +235,22 @@ class DeliveryNote(SellingController):
def check_credit_limit(self): def check_credit_limit(self):
from erpnext.selling.doctype.customer.customer import check_credit_limit from erpnext.selling.doctype.customer.customer import check_credit_limit
extra_amount = 0
validate_against_credit_limit = False validate_against_credit_limit = False
for d in self.get("items"): bypass_credit_limit_check_at_sales_order = cint(frappe.db.get_value("Customer", self.customer,
if not (d.against_sales_order or d.against_sales_invoice): "bypass_credit_limit_check_at_sales_order"))
validate_against_credit_limit = True if bypass_credit_limit_check_at_sales_order:
break validate_against_credit_limit = True
extra_amount = self.base_grand_total
else:
for d in self.get("items"):
if not (d.against_sales_order or d.against_sales_invoice):
validate_against_credit_limit = True
break
if validate_against_credit_limit: if validate_against_credit_limit:
check_credit_limit(self.customer, self.company) check_credit_limit(self.customer, self.company,
bypass_credit_limit_check_at_sales_order, extra_amount)
def validate_packed_qty(self): def validate_packed_qty(self):
""" """

View File

@ -317,11 +317,6 @@ $.extend(erpnext.item, {
show_multiple_variants_dialog: function(frm) { show_multiple_variants_dialog: function(frm) {
var me = this; var me = this;
if(me.multiple_variant_dialog) {
me.multiple_variant_dialog.show();
return;
}
let promises = []; let promises = [];
let attr_val_fields = {}; let attr_val_fields = {};

View File

@ -57,6 +57,21 @@
<div class="item-stock" itemprop="availability"></div> <div class="item-stock" itemprop="availability"></div>
</div> </div>
<div class="item-cart hide"> <div class="item-cart hide">
<div id="item-spinner">
<span style="display: inline-block">
<div class="input-group number-spinner">
<span class="input-group-btn">
<button class="btn btn-default cart-btn" data-dir="dwn">
</button>
</span>
<input class="form-control text-right cart-qty" value="1">
<span class="input-group-btn">
<button class="btn btn-default cart-btn" data-dir="up" style="margin-left:-2px;">
+</button>
</span>
</div>
</span>
</div>
<div id="item-add-to-cart"> <div id="item-add-to-cart">
<button class="btn btn-primary btn-sm"> <button class="btn btn-primary btn-sm">
{{ _("Add to Cart") }}</button> {{ _("Add to Cart") }}</button>

View File

@ -15,7 +15,7 @@ frappe.ready(function() {
$(".item-cart").toggleClass("hide", (!!!r.message.price || !!!r.message.in_stock)); $(".item-cart").toggleClass("hide", (!!!r.message.price || !!!r.message.in_stock));
if(r.message && r.message.price) { if(r.message && r.message.price) {
$(".item-price") $(".item-price")
.html(r.message.price.formatted_price + " {{ _("per") }} " + r.message.uom); .html(r.message.price.formatted_price + " / " + r.message.uom);
if(r.message.in_stock==0) { if(r.message.in_stock==0) {
$(".item-stock").html("<div style='color: red'> <i class='fa fa-close'></i> {{ _("Not in stock") }}</div>"); $(".item-stock").html("<div style='color: red'> <i class='fa fa-close'></i> {{ _("Not in stock") }}</div>");
@ -44,7 +44,7 @@ frappe.ready(function() {
erpnext.shopping_cart.update_cart({ erpnext.shopping_cart.update_cart({
item_code: get_item_code(), item_code: get_item_code(),
qty: 1, qty: $("#item-spinner .cart-qty").val(),
callback: function(r) { callback: function(r) {
if(!r.exc) { if(!r.exc) {
toggle_update_cart(1); toggle_update_cart(1);
@ -55,6 +55,25 @@ frappe.ready(function() {
}); });
}); });
$("#item-spinner").on('click', '.number-spinner button', function () {
var btn = $(this),
input = btn.closest('.number-spinner').find('input'),
oldValue = input.val().trim(),
newVal = 0;
if (btn.attr('data-dir') == 'up') {
newVal = parseInt(oldValue) + 1;
} else if (btn.attr('data-dir') == 'dwn') {
if (parseInt(oldValue) > 1) {
newVal = parseInt(oldValue) - 1;
}
else {
newVal = parseInt(oldValue);
}
}
input.val(newVal);
});
$("[itemscope] .item-view-attribute .form-control").on("change", function() { $("[itemscope] .item-view-attribute .form-control").on("change", function() {
try { try {
var item_code = encodeURIComponent(get_item_code()); var item_code = encodeURIComponent(get_item_code());
@ -86,6 +105,7 @@ var toggle_update_cart = function(qty) {
$("#item-update-cart") $("#item-update-cart")
.toggle(qty ? true : false) .toggle(qty ? true : false)
.find("input").val(qty); .find("input").val(qty);
$("#item-spinner").toggle(qty ? false : true);
} }
function get_item_code() { function get_item_code() {