Compare commits

...

60 Commits

Author SHA1 Message Date
Sahil Khan
57041084d1 Merge branch 'version-11-hotfix' into version-11 2019-08-07 16:17:49 +05:30
Sahil Khan
cf4c7b406a bumped to version 11.1.53 2019-08-07 16:37:49 +05:50
Anurag Mishra
edb3cdbb59 fix: query (#18465) 2019-08-05 10:27:48 +05:30
Mangesh-Khairnar
55025d4dc9 fix(sales-order): update items (#18537)
* fix(sales-order): update items

* fix: minor changes
2019-08-05 10:16:10 +05:30
Deepesh Garg
6f7a5e525d fix: Make conversion rate optional for non itemized items (#18538) 2019-08-05 10:14:37 +05:30
Deepesh Garg
342c77c851 fix: Customer price list not honored in shopping cart (#18555) 2019-08-05 10:11:07 +05:30
Suraj Shetty
94f4d81b70 fix: Quotation list in customer portal (#18580)
* fix: Quotation list in customer portal

Customers were not able to see their quotations because of
 the recent customer field removal from quotation

* fix: Remove duplicate code
2019-08-05 10:08:01 +05:30
Mangesh-Khairnar
74f31c6677 fix(employee-advance): update employee advance on change in expense claim (#18589)
* fix(employee-advance): update employee advance on rejection/cancellation of expense claim

* fix(expense-claim): display appropriate buttons only if linked transactions are valid

* Update employee_advance.py
2019-08-05 10:03:32 +05:30
Deepesh Garg
7589b9f822 Merge pull request #18575 from deepeshgarg007/taxes_and_totals_v11
fix: Error handling in taxes and totals
2019-08-03 18:27:52 +05:30
deepeshgarg007
b761169976 fix: Error handling in taxes and totals 2019-08-03 13:40:37 +05:30
Suraj Shetty
537c3a79ab fix: Show correct label instead of showing undefined (#18561) 2019-08-02 21:46:11 +05:30
Sahil Khan
0510988b3e Merge branch 'version-11-hotfix' into version-11 2019-08-02 14:24:50 +05:30
Sahil Khan
79c389a48f bumped to version 11.1.52 2019-08-02 14:44:50 +05:50
Suraj Shetty
239779dad0 fix: Check zero valuation rate only for valid doctypes (#18530)
fix: Check zero valuation rate only for valid doctypes
2019-08-02 11:23:01 +05:30
Suraj Shetty
37a7ab9d0e Merge branch 'version-11-hotfix' into stock-ledger-fix_v11 2019-08-02 08:42:58 +05:30
Deepesh Garg
17ea15eb91 fix: Enhancement in credit note (#18534)
* fix: Credit note enhancement

* Fix: Print format for Sales Invoice Return

* fix: Zero quantity validation fix for credit note
2019-07-31 15:59:34 +05:30
rohitwaghchaure
ddaf81051e Merge pull request #18532 from rohitwaghchaure/scrap_item_not_adding_v11_hotfix
fix: scrap item not adding while making finished good entry
2019-07-31 12:09:52 +05:30
Rohit Waghchaure
d06f3882e9 fix: scrap item not adding while making finished good entry 2019-07-31 00:00:47 +05:30
deepeshgarg007
523994c2cf fix: Check zero valuation rate only for valid doctypes 2019-07-30 19:06:06 +05:30
Sahil Khan
00219ab7f0 Merge branch 'version-11-hotfix' into version-11 2019-07-30 13:06:37 +05:30
Sahil Khan
0c4f1e5e67 bumped to version 11.1.51 2019-07-30 13:26:37 +05:50
rohitwaghchaure
29e3cfd60f Merge pull request #18456 from rohitwaghchaure/bank_reco_consider_incorrect_paid_amount_field
fix: multi currency bank reconciliation issue
2019-07-29 11:40:25 +05:30
Mangesh-Khairnar
95058eac21 fix(payroll-entry): show make bank entry button when manually submitting salary slip (#18499) 2019-07-27 10:04:18 +05:30
Sahil Khan
4b963be8c4 Merge branch 'version-11-hotfix' into version-11 2019-07-26 12:43:33 +05:30
Sahil Khan
a4434b1a50 bumped to version 11.1.50 2019-07-26 13:03:33 +05:50
Deepesh Garg
d3dc241878 Merge pull request #18492 from adityahase/faster-version-11
perf(travis): Faster Builds
2019-07-26 12:40:27 +05:30
Deepesh Garg
012e617c55 Merge pull request #18479 from deepeshgarg007/bank_reco
fix: Dynamic link issue fix in Bank reconciliation statement
2019-07-26 08:49:14 +05:30
Deepesh Garg
bee135c8c6 Merge pull request #18472 from deepeshgarg007/gstr-1-query
fix: GSTR-1 report fixes
2019-07-26 08:46:24 +05:30
Aditya Hase
8a19cd1096 perf(tests): Remove unnecessary tearDown code 2019-07-26 06:03:21 +05:30
Aditya Hase
5b14ca081c perf(travis): Use setUpClass instead of setUp 2019-07-26 06:03:21 +05:30
Aditya Hase
4fbf03c760 perf(tests): Use country with lesser fixtures 2019-07-26 06:00:51 +05:30
Aditya Hase
503d1d3291 perf(tests): Do not unnecessarily create companies 2019-07-26 05:59:31 +05:30
Aditya Hase
e2108d75dc perf(tests): Use setUpClass and tearDownClass instead of setUp and tearDown 2019-07-26 05:59:31 +05:30
Aditya Hase
9a009a1d1c fix(travis): Install wkhtmltopdf 2019-07-26 05:59:30 +05:30
Aditya Hase
61211486bc fix(travis): Remove redundant arguments 2019-07-26 05:59:30 +05:30
Aditya Hase
42fd7451bf fix(travis): Use --site parameter 2019-07-26 05:59:30 +05:30
Aditya Hase
70a1c1bdac fix(travis): Merge install and before_script 2019-07-26 05:59:30 +05:30
Aditya Hase
eb6d74217c fix(travis): Execute get-app before bench start 2019-07-26 05:59:30 +05:30
Aditya Hase
172a8f1cea perf(travis): Use travis optimizations 2019-07-26 05:59:30 +05:30
Aditya Hase
164057fa5b perf(travis): Do not build assets 2019-07-26 05:59:30 +05:30
Aditya Hase
3c21ac6bb6 perf(travis): Remove unnecessary processes from Procfile 2019-07-26 05:59:30 +05:30
Aditya Hase
ad2585e1b7 fix(travis): Use MariaDB credentials from site_config.json 2019-07-26 05:59:30 +05:30
Aditya Hase
09fe604452 perf(travis): Do not waste 10 seconds 2019-07-26 05:59:30 +05:30
Aditya Hase
28d836cfea fix(travis): Execute get-app and reinstall after bench start 2019-07-26 05:59:30 +05:30
Aditya Hase
8005f350ae fix(travis): Do not disable scheduler using CLI 2019-07-26 05:59:30 +05:30
Aditya Hase
3168ae7dcc fix(travis): Do not use use_site 2019-07-26 05:59:30 +05:30
Aditya Hase
447b8dab96 fix(travis): Setup MariaDB 2019-07-26 05:59:30 +05:30
Aditya Hase
6408949d43 fix(travis): Remove unused files 2019-07-26 05:59:30 +05:30
Aditya Hase
5fe2272861 fix(travis): Remove travis/bench_init.sh 2019-07-26 05:58:21 +05:30
Aditya Hase
7165a5d1f8 fix(travis): Do not execute install.py 2019-07-26 05:58:21 +05:30
Aditya Hase
2ec21fa9fb fix(travis): Install coverall after tests 2019-07-26 05:58:21 +05:30
Aditya Hase
a162f560dd fix(travis): Use MariaDB 10.3 2019-07-26 05:58:21 +05:30
Aditya Hase
c3c168a70d fix(travis): Restructure build matrix 2019-07-26 05:58:21 +05:30
Aditya Hase
b5ac3b8a53 fix(travis): Remove unnecessary commands 2019-07-26 05:58:21 +05:30
deepeshgarg007
30452a8bcd Dynamic link issue fix in Bank reconciliation statement 2019-07-25 12:23:40 +05:30
deepeshgarg007
36676effd9 fix: Query for b2b 2019-07-24 21:19:08 +05:30
deepeshgarg007
f31ff74a75 fix: GSTR-1 report fixes 2019-07-24 20:52:25 +05:30
rohitwaghchaure
4298cc1cdb Merge pull request #18462 from rohitwaghchaure/pos_not_working_for_loyalty
fix: pos not working
2019-07-24 12:22:30 +05:30
Rohit Waghchaure
abe5a5ae30 fix: pos not working 2019-07-24 12:06:12 +05:30
Rohit Waghchaure
9daaeaed07 fix: multi currency bank reco issue 2019-07-23 23:13:33 +05:30
35 changed files with 449 additions and 196 deletions

View File

@@ -1,51 +1,80 @@
language: python
dist: trusty
python:
- "2.7"
- "3.6"
language: python
env:
- TEST_TYPE="Server Side Test"
- TEST_TYPE="Patch Test"
git:
depth: 1
services:
- mysql
cache:
- pip
addons:
hosts: test_site
mariadb: 10.3
jobs:
include:
- name: "Python 2.7 Server Side Test"
python: 2.7
script: bench --site test_site run-tests --app erpnext --coverage
- name: "Python 3.6 Server Side Test"
python: 3.6
script: bench --site test_site run-tests --app erpnext --coverage
- name: "Python 2.7 Patch Test"
python: 2.7
before_script:
- wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz
- bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz
script: bench --site test_site migrate
- name: "Python 3.6 Patch Test"
python: 3.6
before_script:
- wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz
- bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz
script: bench --site test_site migrate
install:
# fix mongodb travis error
- sudo rm /etc/apt/sources.list.d/mongodb*.list
- pip install flake8==3.3.0
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
- sudo rm /etc/apt/sources.list.d/docker.list
- sudo apt-get install hhvm && rm -rf /home/travis/.kiex/
- sudo apt-get purge -y mysql-common mysql-server mysql-client
- cd ~
- nvm install 10
- pip install python-coveralls
- wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py
- sudo python install.py --develop --user travis --without-bench-setup
- sudo pip install -e ~/bench
- rm $TRAVIS_BUILD_DIR/.git/shallow
- bash $TRAVIS_BUILD_DIR/travis/bench_init.sh
- cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/
- git clone https://github.com/frappe/bench --depth 1
- pip install -e ./bench
before_script:
- 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;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root -ptravis
- git clone https://github.com/frappe/frappe --branch $TRAVIS_BRANCH --depth 1
- bench init --skip-assets --frappe-path ~/frappe --python $(which python) frappe-bench
- mkdir ~/frappe-bench/sites/test_site
- cp -r $TRAVIS_BUILD_DIR/.travis/site_config.json ~/frappe-bench/sites/test_site/
- mysql -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
- mysql -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
- mysql -u root -e "CREATE DATABASE test_frappe"
- mysql -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
- mysql -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
- mysql -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
- mysql -u root -e "FLUSH PRIVILEGES"
- wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
- tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
- sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
- sudo chmod o+x /usr/local/bin/wkhtmltopdf
- cd ~/frappe-bench
- bench get-app erpnext $TRAVIS_BUILD_DIR
- bench use test_site
- bench reinstall --mariadb-root-username root --mariadb-root-password travis --yes
- bench scheduler disable
- sed -i 's/9000/9001/g' sites/common_site_config.json
- bench start &
- sleep 10
script:
- bash $TRAVIS_BUILD_DIR/travis/run-tests.sh
- sed -i 's/watch:/# watch:/g' Procfile
- sed -i 's/schedule:/# schedule:/g' Procfile
- sed -i 's/socketio:/# socketio:/g' Procfile
- sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
- bench get-app erpnext $TRAVIS_BUILD_DIR
- bench start &
- bench --site test_site reinstall --yes
after_script:
- pip install python-coveralls
- coveralls -b apps/erpnext -d ../../sites/.coverage

View File

@@ -6,7 +6,8 @@
"mail_login": "test@example.com",
"mail_password": "test",
"admin_password": "admin",
"run_selenium_tests": 1,
"host_name": "http://localhost:8000",
"root_login": "root",
"root_password": "travis",
"host_name": "http://test_site:8000",
"install_apps": ["erpnext"]
}

View File

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

View File

@@ -45,7 +45,7 @@ class BankTransaction(StatusUpdater):
def clear_linked_payment_entries(self):
for payment_entry in self.payment_entries:
allocated_amount = get_total_allocated_amount(payment_entry)
paid_amount = get_paid_amount(payment_entry)
paid_amount = get_paid_amount(payment_entry, self.currency)
if paid_amount and allocated_amount:
if flt(allocated_amount[0]["allocated_amount"]) > flt(paid_amount):
@@ -80,9 +80,17 @@ def get_total_allocated_amount(payment_entry):
AND
bt.docstatus = 1""", (payment_entry.payment_document, payment_entry.payment_entry), as_dict=True)
def get_paid_amount(payment_entry):
def get_paid_amount(payment_entry, currency):
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "paid_amount")
paid_amount_field = "paid_amount"
if payment_entry.payment_document == 'Payment Entry':
doc = frappe.get_doc("Payment Entry", payment_entry.payment_entry)
paid_amount_field = ("base_paid_amount"
if doc.paid_to_account_currency == currency else "paid_amount")
return frappe.db.get_value(payment_entry.payment_document,
payment_entry.payment_entry, paid_amount_field)
elif payment_entry.payment_document == "Journal Entry":
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_credit")

View File

@@ -38,7 +38,10 @@ def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=Non
@frappe.whitelist()
def get_loyalty_program_details_with_points(customer, loyalty_program=None, expiry_date=None, company=None, silent=False, include_expired_entry=False, current_transaction_amount=0):
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program or lp_details.loyalty_program)
loyalty_program_name = loyalty_program or lp_details.loyalty_program
if not loyalty_program_name: return
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program_name)
lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry))
tier_spent_level = sorted([d.as_dict() for d in loyalty_program.collection_rules],

View File

@@ -613,13 +613,18 @@ def get_orders_to_be_billed(posting_date, party_type, party, party_account_curre
orders = []
if voucher_type:
ref_field = "base_grand_total" if party_account_currency == company_currency else "grand_total"
if party_account_currency == company_currency:
grand_total_field = "base_grand_total"
rounded_total_field = "base_rounded_total"
else:
grand_total_field = "grand_total"
rounded_total_field = "rounded_total"
orders = frappe.db.sql("""
select
name as voucher_no,
{ref_field} as invoice_amount,
({ref_field} - advance_paid) as outstanding_amount,
if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
(if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
transaction_date as posting_date
from
`tab{voucher_type}`
@@ -627,17 +632,18 @@ def get_orders_to_be_billed(posting_date, party_type, party, party_account_curre
{party_type} = %s
and docstatus = 1
and ifnull(status, "") != "Closed"
and {ref_field} > advance_paid
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
and abs(100 - per_billed) > 0.01
{condition}
order by
transaction_date, name
""".format(**{
"ref_field": ref_field,
"rounded_total_field": rounded_total_field,
"grand_total_field": grand_total_field,
"voucher_type": voucher_type,
"party_type": scrub(party_type),
"condition": condition
}), party, as_dict=True)
}), (party), as_dict=True)
order_list = []
for d in orders:

View File

@@ -20,11 +20,13 @@ test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Templ
test_ignore = ["Serial No"]
class TestPurchaseInvoice(unittest.TestCase):
def setUp(self):
@classmethod
def setUpClass(self):
unlink_payment_on_cancel_of_invoice()
frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)
def tearDown(self):
@classmethod
def tearDownClass(self):
unlink_payment_on_cancel_of_invoice(0)
def test_gl_entries_without_perpetual_inventory(self):
@@ -91,6 +93,7 @@ class TestPurchaseInvoice(unittest.TestCase):
pi_doc = frappe.get_doc('Purchase Invoice', pi_doc.name)
self.assertRaises(frappe.LinkExistsError, pi_doc.cancel)
unlink_payment_on_cancel_of_invoice()
def test_purchase_invoice_for_blocked_supplier(self):
supplier = frappe.get_doc('Supplier', '_Test Supplier')

View File

@@ -44,6 +44,10 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
this.frm.toggle_reqd("due_date", !this.frm.doc.is_return);
if (this.frm.doc.is_return) {
this.frm.return_print_format = "Sales Invoice Return";
}
this.show_general_ledger();
if(doc.update_stock) this.show_stock_ledger();
@@ -131,16 +135,24 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
},
set_default_print_format: function() {
// set default print format to POS type
// set default print format to POS type or Credit Note
if(cur_frm.doc.is_pos) {
if(cur_frm.pos_print_format) {
cur_frm.meta._default_print_format = cur_frm.meta.default_print_format;
cur_frm.meta.default_print_format = cur_frm.pos_print_format;
}
} else if(cur_frm.doc.is_return) {
if(cur_frm.return_print_format) {
cur_frm.meta._default_print_format = cur_frm.meta.default_print_format;
cur_frm.meta.default_print_format = cur_frm.return_print_format;
}
} else {
if(cur_frm.meta._default_print_format) {
cur_frm.meta.default_print_format = cur_frm.meta._default_print_format;
cur_frm.meta._default_print_format = null;
} else if(in_list([cur_frm.pos_print_format, cur_frm.return_print_format], cur_frm.meta.default_print_format)) {
cur_frm.meta.default_print_format = null;
cur_frm.meta._default_print_format = null;
}
}
},

View File

@@ -28,10 +28,12 @@ class TestSalesInvoice(unittest.TestCase):
w.submit()
return w
def setUp(self):
@classmethod
def setUpClass(self):
unlink_payment_on_cancel_of_invoice()
def tearDown(self):
@classmethod
def tearDownClass(self):
unlink_payment_on_cancel_of_invoice(0)
def test_timestamp_change(self):
@@ -134,6 +136,7 @@ class TestSalesInvoice(unittest.TestCase):
unlink_payment_on_cancel_of_invoice(0)
si = frappe.get_doc('Sales Invoice', si.name)
self.assertRaises(frappe.LinkExistsError, si.cancel)
unlink_payment_on_cancel_of_invoice()
def test_sales_invoice_calculation_export_currency(self):
si = frappe.copy_doc(test_records[2])

View File

@@ -124,8 +124,6 @@ def check_matching_amount(bank_account, company, transaction):
'txt': '%%%s%%' % amount
}, as_dict=True)
frappe.errprint(journal_entries)
if transaction.credit > 0:
sales_invoices = frappe.db.sql("""
SELECT

View File

@@ -0,0 +1,129 @@
{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value, fieldmeta,
get_width, get_align_class -%}
{%- macro render_currency(df, doc) -%}
<div class="row {% if df.bold %}important{% endif %} data-field">
<div class="col-xs-{{ "9" if df.fieldtype=="Check" else "5" }}
{%- if doc._align_labels_right %} text-right{%- endif -%}">
<label>{{ _(df.label) }}</label>
</div>
<div class="col-xs-{{ "3" if df.fieldtype=="Check" else "7" }} value">
{% if doc.get(df.fieldname) != None -%}
{{ frappe.utils.fmt_money((doc[df.fieldname])|int|abs, currency=doc.currency) }}
{% endif %}
</div>
</div>
{%- endmacro -%}
{%- macro render_taxes(df, doc) -%}
{%- set data = doc.get(df.fieldname)[df.start:df.end] -%}
<div class="row">
<div class="col-xs-6"></div>
<div class="col-xs-6">
{%- for charge in data -%}
{%- if (charge.tax_amount or doc.flags.print_taxes_with_zero_amount) and (not charge.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
<div class="row">
<div class="col-xs-5 {%- if doc._align_labels_right %} text-right{%- endif -%}">
<label>{{ charge.get_formatted("description") }}</label></div>
<div class="col-xs-7 text-right">
{{ frappe.utils.fmt_money((charge.tax_amount)|int|abs, currency=doc.currency) }}
</div>
</div>
{%- endif -%}
{%- endfor -%}
</div>
</div>
{%- endmacro -%}
{%- macro render_table(df, doc) -%}
{%- set table_meta = frappe.get_meta(df.options) -%}
{%- set data = doc.get(df.fieldname)[df.start:df.end] -%}
{%- if doc.print_templates and
doc.print_templates.get(df.fieldname) -%}
{% include doc.print_templates[df.fieldname] %}
{%- else -%}
{%- if data -%}
{%- set visible_columns = get_visible_columns(doc.get(df.fieldname),
table_meta, df) -%}
<div {{ fieldmeta(df) }}>
<table class="table table-bordered table-condensed">
<thead>
<tr>
<th style="width: 40px" class="table-sr">{{ _("Sr") }}</th>
{% for tdf in visible_columns %}
{% if (data and not data[0].flags.compact_item_print) or tdf.fieldname in doc.get(df.fieldname)[0].flags.compact_item_fields %}
<th style="width: {{ get_width(tdf) }};" class="{{ get_align_class(tdf) }}" {{ fieldmeta(df) }}>
{{ _(tdf.label) }}</th>
{% endif %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for d in data %}
<tr>
<td class="table-sr">{{ d.idx }}</td>
{% for tdf in visible_columns %}
{% if not d.flags.compact_item_print or tdf.fieldname in doc.get(df.fieldname)[0].flags.compact_item_fields %}
<td class="{{ get_align_class(tdf) }}" {{ fieldmeta(df) }}>
{% if tdf.fieldtype == 'Currency' %}
<div class="value">{{ frappe.utils.fmt_money((d[tdf.fieldname])|int|abs, currency=doc.currency) }}</div></td>
{% else %}
<div class="value">{{ print_value(tdf, d, doc, visible_columns) }}</div></td>
{% endif %}
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{%- endif -%}
{%- endif -%}
{%- endmacro -%}
{% for page in layout %}
<div class="page-break">
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}
</div>
{% if print_settings.repeat_header_footer %}
<div id="footer-html" class="visible-pdf">
{% if not no_letterhead and footer %}
<div class="letter-head-footer">
{{ footer }}
</div>
{% endif %}
<p class="text-center small page-number visible-pdf">
{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
</p>
</div>
{% endif %}
{% for section in page %}
<div class="row section-break">
{% if section.columns.fields %}
{%- if doc._line_breaks and loop.index != 1 -%}<hr>{%- endif -%}
{%- if doc._show_section_headings and section.label and section.has_data -%}
<h4 class='col-sm-12'>{{ _(section.label) }}</h4>
{% endif %}
{%- endif -%}
{% for column in section.columns %}
<div class="col-xs-{{ (12 / section.columns|len)|int }} column-break">
{% for df in column.fields %}
{% if df.fieldname == 'taxes' %}
{{ render_taxes(df, doc) }}
{% elif df.fieldtype == 'Currency' %}
{{ render_currency(df, doc) }}
{% elif df.fieldtype =='Table' %}
{{ render_table(df, doc)}}
{% elif doc[df.fieldname] %}
{{ render_field(df, doc) }}
{% endif %}
{% endfor %}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endfor %}

View File

@@ -0,0 +1,24 @@
{
"align_labels_right": 1,
"creation": "2019-07-24 20:13:30.259953",
"custom_format": 0,
"default_print_language": "en-US",
"disabled": 0,
"doc_type": "Sales Invoice",
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
"html": "",
"idx": 0,
"line_breaks": 1,
"modified": "2019-07-24 20:13:30.259953",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Return",
"owner": "Administrator",
"print_format_builder": 0,
"print_format_type": "Jinja",
"raw_printing": 0,
"show_section_headings": 1,
"standard": "Yes"
}

View File

@@ -58,8 +58,7 @@ def get_columns():
{
"fieldname": "payment_document",
"label": _("Payment Document Type"),
"fieldtype": "Link",
"options": "DocType",
"fieldtype": "Data",
"width": 220
},
{

View File

@@ -30,7 +30,9 @@ def update_last_purchase_rate(doc, is_submit):
# for it to be considered for latest purchase rate
if flt(d.conversion_factor):
last_purchase_rate = flt(d.base_rate) / flt(d.conversion_factor)
else:
# Check if item code is present
# Conversion factor should not be mandatory for non itemized items
elif d.item_code:
frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx))
# update last purchsae rate
@@ -84,13 +86,13 @@ def get_linked_material_requests(items):
items = json.loads(items)
mr_list = []
for item in items:
material_request = frappe.db.sql("""SELECT distinct mr.name AS mr_name,
(mr_item.qty - mr_item.ordered_qty) AS qty,
material_request = frappe.db.sql("""SELECT distinct mr.name AS mr_name,
(mr_item.qty - mr_item.ordered_qty) AS qty,
mr_item.item_code AS item_code,
mr_item.name AS mr_item
mr_item.name AS mr_item
FROM `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
WHERE mr.name = mr_item.parent
AND mr_item.item_code = %(item)s
AND mr_item.item_code = %(item)s
AND mr.material_request_type = 'Purchase'
AND mr.per_ordered < 99.99
AND mr.docstatus = 1
@@ -98,6 +100,6 @@ def get_linked_material_requests(items):
ORDER BY mr_item.item_code ASC""",{"item": item}, as_dict=1)
if material_request:
mr_list.append(material_request)
return mr_list

View File

@@ -58,7 +58,9 @@ class AccountsController(TransactionBase):
def validate(self):
self.validate_qty_is_not_zero()
if not self.get('is_return'):
self.validate_qty_is_not_zero()
if self.get("_action") and self._action != "update_after_submit":
self.set_missing_values(for_validate=True)
@@ -1110,6 +1112,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name):
.format(child_item.idx, child_item.item_code))
else:
child_item.rate = flt(d.get("rate"))
if flt(child_item.price_list_rate):
child_item.discount_percentage = flt((1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0, \
child_item.precision("discount_percentage"))
child_item.flags.ignore_validate_update_after_submit = True
child_item.save()

View File

@@ -395,7 +395,9 @@ class BuyingController(StockController):
def set_qty_as_per_stock_uom(self):
for d in self.get("items"):
if d.meta.get_field("stock_qty"):
if not d.conversion_factor:
# Check if item code is present
# Conversion factor should not be mandatory for non itemized items
if not d.conversion_factor and d.item_code:
frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx))
d.stock_qty = flt(d.qty) * flt(d.conversion_factor)

View File

@@ -76,7 +76,12 @@ class calculate_taxes_and_totals(object):
item.discount_amount = item.price_list_rate - item.rate
item.net_rate = item.rate
item.amount = flt(item.rate * item.qty, item.precision("amount"))
if not item.qty and self.doc.get("is_return"):
item.amount = flt(-1 * item.rate, item.precision("amount"))
else:
item.amount = flt(item.rate * item.qty, item.precision("amount"))
item.net_amount = item.amount
self._set_in_company_currency(item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"])

View File

@@ -77,7 +77,7 @@ def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_len
if or_filters:
for r in frappe.get_list(doctype, fields=fields,filters=filters, or_filters=or_filters,
limit_start=limit_start, limit_page_length=limit_page_length,
limit_start=limit_start, limit_page_length=limit_page_length,
ignore_permissions=ignore_permissions, order_by=order_by):
data.append(r)
@@ -130,38 +130,56 @@ def get_customers_suppliers(doctype, user):
suppliers = []
meta = frappe.get_meta(doctype)
customer_field_name = get_customer_field_name(doctype)
has_customer_field = meta.has_field(customer_field_name)
has_supplier_field = meta.has_field('supplier')
if has_common(["Supplier", "Customer"], frappe.get_roles(user)):
contacts = frappe.db.sql("""
select
select
`tabContact`.email_id,
`tabDynamic Link`.link_doctype,
`tabDynamic Link`.link_name
from
from
`tabContact`, `tabDynamic Link`
where
`tabContact`.name=`tabDynamic Link`.parent and `tabContact`.email_id =%s
""", user, as_dict=1)
customers = [c.link_name for c in contacts if c.link_doctype == 'Customer'] \
if meta.get_field("customer") else None
suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier'] \
if meta.get_field("supplier") else None
customers = [c.link_name for c in contacts if c.link_doctype == 'Customer']
suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier']
elif frappe.has_permission(doctype, 'read', user=user):
customers = [customer.name for customer in frappe.get_list("Customer")] \
if meta.get_field("customer") else None
suppliers = [supplier.name for supplier in frappe.get_list("Customer")] \
if meta.get_field("supplier") else None
customer_list = frappe.get_list("Customer")
customers = suppliers = [customer.name for customer in customer_list]
return customers, suppliers
return customers if has_customer_field else None, \
suppliers if has_supplier_field else None
def has_website_permission(doc, ptype, user, verbose=False):
doctype = doc.doctype
customers, suppliers = get_customers_suppliers(doctype, user)
if customers:
return frappe.get_all(doctype, filters=[(doctype, "customer", "in", customers),
(doctype, "name", "=", doc.name)]) and True or False
return frappe.db.exists(doctype, filters=get_customer_filter(doc, customers))
elif suppliers:
fieldname = 'suppliers' if doctype == 'Request for Quotation' else 'supplier'
return frappe.get_all(doctype, filters=[(doctype, fieldname, "in", suppliers),
(doctype, "name", "=", doc.name)]) and True or False
return frappe.db.exists(doctype, filters={
'name': doc.name,
fieldname: ["in", suppliers]
})
else:
return False
def get_customer_filter(doc, customers):
doctype = doc.doctype
filters = frappe._dict()
filters.name = doc.name
filters[get_customer_field_name(doctype)] = ['in', customers]
if doctype == 'Quotation':
filters.party_type = 'Customer'
return filters
def get_customer_field_name(doctype):
if doctype == 'Quotation':
return 'party_name'
else:
return 'customer'

View File

@@ -64,13 +64,20 @@ class EmployeeAdvance(Document):
def update_claimed_amount(self):
claimed_amount = frappe.db.sql("""
select sum(ifnull(allocated_amount, 0))
from `tabExpense Claim Advance`
where employee_advance = %s and docstatus=1 and allocated_amount > 0
SELECT sum(ifnull(allocated_amount, 0))
FROM `tabExpense Claim Advance` eca, `tabExpense Claim` ec
WHERE
eca.employee_advance = %s
AND ec.approval_status="Approved"
AND ec.name = eca.parent
AND ec.docstatus=1
AND eca.allocated_amount > 0
""", self.name)[0][0] or 0
if claimed_amount:
frappe.db.set_value("Employee Advance", self.name, "claimed_amount", flt(claimed_amount))
frappe.db.set_value("Employee Advance", self.name, "claimed_amount", flt(claimed_amount))
self.reload()
self.set_status()
frappe.db.set_value("Employee Advance", self.name, "status", self.status)
@frappe.whitelist()
def get_due_advance_amount(employee, posting_date):

View File

@@ -178,7 +178,7 @@ frappe.ui.form.on("Expense Claim", {
refresh: function(frm) {
frm.trigger("toggle_fields");
if(frm.doc.docstatus == 1) {
if(frm.doc.docstatus === 1 && frm.doc.approval_status !== "Rejected") {
frm.add_custom_button(__('Accounting Ledger'), function() {
frappe.route_options = {
voucher_no: frm.doc.name,
@@ -189,7 +189,7 @@ frappe.ui.form.on("Expense Claim", {
}, __("View"));
}
if (frm.doc.docstatus===1
if (frm.doc.docstatus===1 && !cint(frm.doc.is_paid) && cint(frm.doc.grand_total) > 0
&& (cint(frm.doc.total_amount_reimbursed) < cint(frm.doc.total_sanctioned_amount))
&& frappe.model.can_create("Payment Entry")) {
frm.add_custom_button(__('Payment'),

View File

@@ -12,6 +12,16 @@ from erpnext.accounts.utils import get_fiscal_year
from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
class PayrollEntry(Document):
def onload(self):
if not self.docstatus==1:
return
# check if salary slips were manually submitted
entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name'])
if cint(entries) == len(self.employees) and not self.salary_slips_submitted:
self.db_set("salary_slips_submitted", 1)
self.reload()
def on_submit(self):
self.create_salary_slips()

View File

@@ -92,5 +92,5 @@ def make_company():
company.abbr = "_TC10"
company.parent_company = "_Test Company"
company.default_currency = "INR"
company.country = "India"
company.country = "Pakistan"
company.insert()

View File

@@ -91,7 +91,13 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
$.each(this.frm.doc["items"] || [], function(i, item) {
frappe.model.round_floats_in(item);
item.net_rate = item.rate;
item.amount = flt(item.rate * item.qty, precision("amount", item));
if ((!item.qty) && me.frm.doc.is_return) {
item.amount = flt(item.rate * -1, precision("amount", item));
} else {
item.amount = flt(item.rate * item.qty, precision("amount", item));
}
item.net_amount = item.amount;
item.item_tax_amount = 0.0;
item.total_weight = flt(item.weight_per_unit * item.stock_qty);

View File

@@ -152,7 +152,7 @@ class Gstr1Report(object):
customers = frappe.get_all("Customer", filters={"disabled": 0})
if self.filters.get("type_of_business") == "B2B":
if self.filters.get("type_of_business") == "B2B" and customers:
conditions += """ and ifnull(invoice_type, '') != 'Export' and is_return != 1
and customer in ('{0}') and (customer_gstin IS NOT NULL AND customer_gstin NOT IN ('', 'NA'))""".\
format("', '".join([frappe.db.escape(c.name) for c in customers]))
@@ -162,14 +162,14 @@ class Gstr1Report(object):
if not b2c_limit:
frappe.throw(_("Please set B2C Limit in GST Settings."))
if self.filters.get("type_of_business") == "B2C Large":
if self.filters.get("type_of_business") == "B2C Large" and customers:
conditions += """ and SUBSTR(place_of_supply, 1, 2) != SUBSTR(company_gstin, 1, 2)
and grand_total > {0} and is_return != 1 and customer in ('{1}')""".\
and grand_total > {0} and is_return != 1 and customer in ('{1}') and (customer_gstin IS NULL OR customer_gstin IN ('', 'NA'))""".\
format(flt(b2c_limit), "', '".join([frappe.db.escape(c.name) for c in customers]))
elif self.filters.get("type_of_business") == "B2C Small":
elif self.filters.get("type_of_business") == "B2C Small" and customers:
conditions += """ and (
SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2)
or grand_total <= {0}) and is_return != 1 and customer in ('{1}')""".\
or grand_total <= {0}) and is_return != 1 and customer in ('{1}') and (customer_gstin IS NULL OR customer_gstin IN ('', 'NA'))""".\
format(flt(b2c_limit), "', '".join([frappe.db.escape(c.name) for c in customers]))
elif self.filters.get("type_of_business") == "CDNR":

View File

@@ -262,27 +262,44 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
});
return;
} else {
var fields = [
{fieldtype:'Table', fieldname: 'items',
description: __('Select BOM and Qty for Production'),
fields: [
{fieldtype:'Read Only', fieldname:'item_code',
label: __('Item Code'), in_list_view:1},
{fieldtype:'Link', fieldname:'bom', options: 'BOM', reqd: 1,
label: __('Select BOM'), in_list_view:1, get_query: function(doc) {
return {filters: {item: doc.item_code}};
}},
{fieldtype:'Float', fieldname:'pending_qty', reqd: 1,
label: __('Qty'), in_list_view:1},
{fieldtype:'Data', fieldname:'sales_order_item', reqd: 1,
label: __('Sales Order Item'), hidden:1}
],
data: r.message,
get_data: function() {
return r.message
const fields = [{
label: 'Items',
fieldtype: 'Table',
fieldname: 'items',
description: __('Select BOM and Qty for Production'),
fields: [{
fieldtype: 'Read Only',
fieldname: 'item_code',
label: __('Item Code'),
in_list_view: 1
}, {
fieldtype: 'Link',
fieldname: 'bom',
options: 'BOM',
reqd: 1,
label: __('Select BOM'),
in_list_view: 1,
get_query: function (doc) {
return { filters: { item: doc.item_code } };
}
}, {
fieldtype: 'Float',
fieldname: 'pending_qty',
reqd: 1,
label: __('Qty'),
in_list_view: 1
}, {
fieldtype: 'Data',
fieldname: 'sales_order_item',
reqd: 1,
label: __('Sales Order Item'),
hidden: 1
}],
data: r.message,
get_data: () => {
return r.message
}
]
}]
var d = new frappe.ui.Dialog({
title: __('Select Items to Manufacture'),
fields: fields,

View File

@@ -202,13 +202,12 @@ def _get_cart_quotation(party=None):
if quotation:
qdoc = frappe.get_doc("Quotation", quotation[0].name)
else:
[company, price_list] = frappe.db.get_value("Shopping Cart Settings", None, ["company", "price_list"])
company = frappe.db.get_value("Shopping Cart Settings", None, ["company"])
qdoc = frappe.get_doc({
"doctype": "Quotation",
"naming_series": get_shopping_cart_settings().quotation_series or "QTN-CART-",
"quotation_to": party.doctype,
"company": company,
"selling_price_list": price_list,
"order_type": "Shopping Cart",
"status": "Draft",
"docstatus": 0,

View File

@@ -753,13 +753,13 @@ class StockEntry(StockController):
self.add_to_stock_entry_detail(item_dict)
if self.purpose != "Subcontract":
scrap_item_dict = self.get_bom_scrap_material(self.fg_completed_qty)
for item in itervalues(scrap_item_dict):
if self.pro_doc and self.pro_doc.scrap_warehouse:
item["to_warehouse"] = self.pro_doc.scrap_warehouse
if self.purpose != "Subcontract" and self.purpose == "Manufacture":
scrap_item_dict = self.get_bom_scrap_material(self.fg_completed_qty)
for item in itervalues(scrap_item_dict):
if self.pro_doc and self.pro_doc.scrap_warehouse:
item["to_warehouse"] = self.pro_doc.scrap_warehouse
self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no)
self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no)
# fetch the serial_no of the first stock entry for the second stock entry
if self.work_order and self.purpose == "Manufacture":

View File

@@ -15,9 +15,10 @@ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.doctype.item.test_item import create_item
class TestStockReconciliation(unittest.TestCase):
def setUp(self):
@classmethod
def setUpClass(self):
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
self.insert_existing_sle()
insert_existing_sle()
def test_reco_for_fifo(self):
self._test_reco_sle_gle("FIFO")
@@ -94,17 +95,17 @@ class TestStockReconciliation(unittest.TestCase):
self.assertEqual(["_Test Stock Reco Item", "_Test Warehouse Ledger 1 - _TC", 100],
[items[0]["item_code"], items[0]["warehouse"], items[0]["qty"]])
def insert_existing_sle(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
def insert_existing_sle():
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
make_stock_entry(posting_date="2012-12-15", posting_time="02:00", item_code="_Test Item",
target="_Test Warehouse - _TC", qty=10, basic_rate=700)
make_stock_entry(posting_date="2012-12-15", posting_time="02:00", item_code="_Test Item",
target="_Test Warehouse - _TC", qty=10, basic_rate=700)
make_stock_entry(posting_date="2012-12-25", posting_time="03:00", item_code="_Test Item",
source="_Test Warehouse - _TC", qty=15)
make_stock_entry(posting_date="2012-12-25", posting_time="03:00", item_code="_Test Item",
source="_Test Warehouse - _TC", qty=15)
make_stock_entry(posting_date="2013-01-05", posting_time="07:00", item_code="_Test Item",
target="_Test Warehouse - _TC", qty=15, basic_rate=1200)
make_stock_entry(posting_date="2013-01-05", posting_time="07:00", item_code="_Test Item",
target="_Test Warehouse - _TC", qty=15, basic_rate=1200)
def create_stock_reconciliation(**args):
args = frappe._dict(args)

View File

@@ -8,16 +8,7 @@ import unittest
class TestStockSettings(unittest.TestCase):
def setUp(self):
settings = frappe.get_single('Stock Settings')
settings.clean_description_html = 0
settings.save()
frappe.delete_doc('Item', 'Item for description test')
def tearDown(self):
settings = frappe.get_single('Stock Settings')
settings.clean_description_html = 1
settings.save()
frappe.db.set_value("Stock Settings", None, "clean_description_html", 0)
def test_settings(self):
item = frappe.get_doc(dict(

View File

@@ -360,8 +360,17 @@ class update_entries_after(object):
self.stock_queue.append([0, sle.incoming_rate or sle.outgoing_rate or self.valuation_rate])
def check_if_allow_zero_valuation_rate(self, voucher_type, voucher_detail_no):
ref_item_dt = voucher_type + (" Detail" if voucher_type == "Stock Entry" else " Item")
return frappe.db.get_value(ref_item_dt, voucher_detail_no, "allow_zero_valuation_rate")
ref_item_dt = ""
if voucher_type == "Stock Entry":
ref_item_dt = voucher_type + " Detail"
elif voucher_type in ["Purchase Invoice", "Sales Invoice", "Delivery Note", "Purchase Receipt"]:
ref_item_dt = voucher_type + " Item"
if ref_item_dt:
return frappe.db.get_value(ref_item_dt, voucher_detail_no, "allow_zero_valuation_rate")
else:
return 0
def get_sle_before_datetime(self):
"""get previous stock ledger entry before current time-bucket"""

View File

@@ -2,45 +2,31 @@
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe
import unittest
from frappe.desk import notifications
from frappe.test_runner import make_test_objects
class TestNotifications(unittest.TestCase):
def setUp(self):
test_records_company = [
{
"abbr": "_TC6",
"company_name": "_Test Company 6",
"country": "India",
"default_currency": "INR",
"doctype": "Company",
"domain": "Manufacturing",
"monthly_sales_target": 2000,
"chart_of_accounts": "Standard"
},
{
"abbr": "_TC7",
"company_name": "_Test Company 7",
"country": "United States",
"default_currency": "USD",
"doctype": "Company",
"domain": "Retail",
"monthly_sales_target": 10000,
"total_monthly_sales": 1000,
"chart_of_accounts": "Standard"
},
]
make_test_objects('Company', test_records=test_records_company, reset=True)
def test_get_notifications_for_targets(self):
'''
Test notification config entries for targets as percentages
'''
company = frappe.get_all("Company")[0]
frappe.db.set_value("Company", company.name, "monthly_sales_target", 10000)
frappe.db.set_value("Company", company.name, "total_monthly_sales", 1000)
config = notifications.get_notification_config()
doc_target_percents = notifications.get_notifications_for_targets(config, {})
self.assertEqual(doc_target_percents['Company']['_Test Company 7'], 10)
self.assertEqual(doc_target_percents['Company']['_Test Company 6'], 0)
self.assertEqual(doc_target_percents['Company'][company.name], 10)
frappe.db.set_value("Company", company.name, "monthly_sales_target", 2000)
frappe.db.set_value("Company", company.name, "total_monthly_sales", 0)
config = notifications.get_notification_config()
doc_target_percents = notifications.get_notifications_for_targets(config, {})
self.assertEqual(doc_target_percents['Company'][company.name], 0)

View File

@@ -1 +0,0 @@
erpnext

View File

@@ -1,8 +0,0 @@
#!/bin/bash
cd ~/
curl -I https://github.com/frappe/frappe/tree/$TRAVIS_BRANCH | head -n 1 | cut -d $' ' -f2 | (
read response;
[ $response == '200' ] && branch=$TRAVIS_BRANCH || branch='develop';
bench init frappe-bench --frappe-path https://github.com/frappe/frappe.git --frappe-branch $branch --python $(which python)
)

View File

@@ -1,12 +0,0 @@
#!/bin/bash
set -e
if [[ $TEST_TYPE == 'Server Side Test' ]]; then
bench run-tests --app erpnext --coverage
elif [[ $TEST_TYPE == 'Patch Test' ]]; then
wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz
bench --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz --mariadb-root-password travis
bench migrate
fi