Merge branch 'develop' of github.com:frappe/erpnext into refactor-call-popup

This commit is contained in:
Suraj Shetty 2019-08-04 22:23:03 +05:30
commit 35c4b78e66
67 changed files with 1383 additions and 1832 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,8 +6,8 @@
"mail_login": "test@example.com",
"mail_password": "test",
"admin_password": "admin",
"run_selenium_tests": 1,
"root_login": "root",
"root_password": "travis",
"host_name": "http://localhost:8000",
"host_name": "http://test_site:8000",
"install_apps": ["erpnext"]
}

View File

@ -123,7 +123,9 @@ class Account(NestedSet):
doc.flags.ignore_root_company_validation = True
doc.update({
"company": company,
"account_currency": None,
# parent account's currency should be passed down to child account's curreny
# if it is None, it picks it up from default company currency, which might be unintended
"account_currency": self.account_currency,
"parent_account": parent_acc_name_map[company]
})
doc.save()

View File

@ -4,45 +4,52 @@
frappe.ui.form.on('Accounting Dimension', {
refresh: function(frm) {
if (!frm.is_new()) {
frm.add_custom_button(__('Show {0}', [frm.doc.document_type]), function () {
frappe.set_route("List", frm.doc.document_type);
});
}
frm.set_query('document_type', () => {
return {
filters: {
name: ['not in', ['Accounting Dimension', 'Project', 'Cost Center']]
name: ['not in', ['Accounting Dimension', 'Project', 'Cost Center', 'Accounting Dimension Detail']]
}
};
});
let button = frm.doc.disabled ? "Enable" : "Disable";
frm.add_custom_button(__(button), function() {
frm.set_value('disabled', 1 - frm.doc.disabled);
frappe.call({
method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.disable_dimension",
args: {
doc: frm.doc
},
freeze: true,
callback: function(r) {
let message = frm.doc.disabled ? "Dimension Disabled" : "Dimension Enabled";
frm.save();
frappe.show_alert({message:__(message), indicator:'green'});
}
if (!frm.is_new()) {
frm.add_custom_button(__('Show {0}', [frm.doc.document_type]), function () {
frappe.set_route("List", frm.doc.document_type);
});
});
let button = frm.doc.disabled ? "Enable" : "Disable";
frm.add_custom_button(__(button), function() {
frm.set_value('disabled', 1 - frm.doc.disabled);
frappe.call({
method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.disable_dimension",
args: {
doc: frm.doc
},
freeze: true,
callback: function(r) {
let message = frm.doc.disabled ? "Dimension Disabled" : "Dimension Enabled";
frm.save();
frappe.show_alert({message:__(message), indicator:'green'});
}
});
});
}
},
document_type: function(frm) {
frm.set_value('label', frm.doc.document_type);
frm.set_value('fieldname', frappe.model.scrub(frm.doc.document_type));
if (frm.is_new()){
let row = frappe.model.add_child(frm.doc, "Accounting Dimension Detail", "dimension_defaults");
row.reference_document = frm.doc.document_type;
frm.refresh_fields("dimension_defaults");
}
frappe.db.get_value('Accounting Dimension', {'document_type': frm.doc.document_type}, 'document_type', (r) => {
if (r && r.document_type) {
frm.set_df_property('document_type', 'description', "Document type is already set as dimension");
@ -50,3 +57,10 @@ frappe.ui.form.on('Accounting Dimension', {
});
},
});
frappe.ui.form.on('Accounting Dimension Detail', {
dimension_defaults_add: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
row.reference_document = frm.doc.document_type;
}
});

View File

@ -1,6 +1,4 @@
{
"_comments": "[]",
"_liked_by": "[]",
"autoname": "field:label",
"creation": "2019-05-04 18:13:37.002352",
"doctype": "DocType",
@ -9,8 +7,7 @@
"document_type",
"label",
"fieldname",
"mandatory_for_bs",
"mandatory_for_pl",
"dimension_defaults",
"disabled"
],
"fields": [
@ -43,19 +40,13 @@
"read_only": 1
},
{
"default": "0",
"fieldname": "mandatory_for_bs",
"fieldtype": "Check",
"label": "Mandatory For Balance Sheet"
},
{
"default": "0",
"fieldname": "mandatory_for_pl",
"fieldtype": "Check",
"label": "Mandatory For Profit and Loss Account"
"fieldname": "dimension_defaults",
"fieldtype": "Table",
"label": "Dimension Defaults",
"options": "Accounting Dimension Detail"
}
],
"modified": "2019-07-14 17:25:01.307948",
"modified": "2019-07-17 16:49:31.134385",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting Dimension",

View File

@ -96,13 +96,13 @@ def delete_accounting_dimension(doc):
frappe.db.sql("""
DELETE FROM `tabCustom Field`
WHERE fieldname = %s
WHERE fieldname = %s
AND dt IN (%s)""" % #nosec
('%s', ', '.join(['%s']* len(doclist))), tuple([doc.fieldname] + doclist))
frappe.db.sql("""
DELETE FROM `tabProperty Setter`
WHERE field_name = %s
WHERE field_name = %s
AND doc_type IN (%s)""" % #nosec
('%s', ', '.join(['%s']* len(doclist))), tuple([doc.fieldname] + doclist))
@ -150,14 +150,40 @@ def get_doctypes_with_dimensions():
"Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item",
"Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
"Travel Request", "Fees", "POS Profile"]
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
"Subscription Plan"]
return doclist
def get_accounting_dimensions(as_list=True):
accounting_dimensions = frappe.get_all("Accounting Dimension", fields=["label", "fieldname", "mandatory_for_pl", "mandatory_for_bs", "disabled"], filters={"disabled": 0})
accounting_dimensions = frappe.get_all("Accounting Dimension", fields=["label", "fieldname", "disabled"])
if as_list:
return [d.fieldname for d in accounting_dimensions]
else:
return accounting_dimensions
def get_checks_for_pl_and_bs_accounts():
dimensions = frappe.db.sql("""SELECT p.label, p.disabled, p.fieldname, c.company, c.mandatory_for_pl, c.mandatory_for_bs
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
WHERE p.name = c.parent""", as_dict=1)
return dimensions
@frappe.whitelist()
def get_dimension_filters():
dimension_filters = frappe.db.sql("""
SELECT label, fieldname, document_type
FROM `tabAccounting Dimension`
WHERE disabled = 0
""", as_dict=1)
default_dimensions = frappe.db.sql("""SELECT parent, company, default_dimension
FROM `tabAccounting Dimension Detail`""", as_dict=1)
default_dimensions_map = {}
for dimension in default_dimensions:
default_dimensions_map.setdefault(dimension['company'], {})
default_dimensions_map[dimension['company']][dimension['parent']] = dimension['default_dimension']
return dimension_filters, default_dimensions_map

View File

@ -27,12 +27,20 @@ class TestAccountingDimension(unittest.TestCase):
dimension1 = frappe.get_doc({
"doctype": "Accounting Dimension",
"document_type": "Location",
"mandatory_for_pl": 1
}).insert()
})
dimension1.append("dimension_defaults", {
"company": "_Test Company",
"reference_document": "Location",
"default_dimension": "Block 1",
"mandatory_for_bs": 1
})
dimension1.insert()
dimension1.save()
else:
dimension1 = frappe.get_doc("Accounting Dimension", "Location")
dimension1.disabled = 0
dimension1.mandatory_for_pl = 1
dimension1.save()
def test_dimension_against_sales_invoice(self):
@ -100,7 +108,6 @@ def disable_dimension():
dimension1.save()
dimension2 = frappe.get_doc("Accounting Dimension", "Location")
dimension2.mandatory_for_pl = 0
dimension2.disabled = 1
dimension2.save()

View File

@ -0,0 +1,67 @@
{
"creation": "2019-07-16 17:53:18.718831",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company",
"reference_document",
"default_dimension",
"mandatory_for_bs",
"mandatory_for_pl"
],
"fields": [
{
"columns": 2,
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"fieldname": "reference_document",
"fieldtype": "Link",
"hidden": 1,
"label": "Reference Document",
"options": "DocType",
"read_only": 1
},
{
"columns": 2,
"fieldname": "default_dimension",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"label": "Default Dimension",
"options": "reference_document",
"reqd": 1
},
{
"columns": 3,
"default": "0",
"fieldname": "mandatory_for_bs",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Mandatory For Balance Sheet"
},
{
"columns": 3,
"default": "0",
"fieldname": "mandatory_for_pl",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Mandatory For Profit and Loss Account"
}
],
"istable": 1,
"modified": "2019-07-17 23:34:33.026883",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting Dimension Detail",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class AccountingDimensionDetail(Document):
pass

View File

@ -10,9 +10,6 @@ def get_data():
{
'label': _('Bank Deatils'),
'items': ['Bank Account', 'Bank Guarantee']
},
{
'items': ['Payment Order']
}
]
}

View File

@ -12,7 +12,7 @@ from erpnext.accounts.party import validate_party_gle_currency, validate_party_f
from erpnext.accounts.utils import get_account_currency
from erpnext.accounts.utils import get_fiscal_year
from erpnext.exceptions import InvalidAccountCurrency
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts
exclude_from_linked_with = True
class GLEntry(Document):
@ -86,16 +86,16 @@ class GLEntry(Document):
account_type = frappe.db.get_value("Account", self.account, "report_type")
for dimension in get_accounting_dimensions(as_list=False):
for dimension in get_checks_for_pl_and_bs_accounts():
if account_type == "Profit and Loss" \
and dimension.mandatory_for_pl and not dimension.disabled:
and self.company == dimension.company and dimension.mandatory_for_pl and not dimension.disabled:
if not self.get(dimension.fieldname):
frappe.throw(_("Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}.")
.format(dimension.label, self.account))
if account_type == "Balance Sheet" \
and dimension.mandatory_for_bs and not dimension.disabled:
and self.company == dimension.company and dimension.mandatory_for_bs and not dimension.disabled:
if not self.get(dimension.fieldname):
frappe.throw(_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.")
.format(dimension.label, self.account))

View File

@ -258,6 +258,7 @@
"print_hide": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
@ -269,12 +270,13 @@
],
"idx": 1,
"istable": 1,
"modified": "2019-05-25 22:14:02.715509",
"modified": "2019-07-16 17:12:08.238334",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -10,7 +10,9 @@
"create_missing_party",
"column_break_3",
"invoice_type",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"section_break_4",
"invoices"
],
@ -59,11 +61,21 @@
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
}
],
"hide_toolbar": 1,
"issingle": 1,
"modified": "2019-06-13 11:45:31.405267",
"modified": "2019-07-25 14:57:33.187689",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Opening Invoice Creation Tool",

View File

@ -7,6 +7,7 @@ import frappe
from frappe import _, scrub
from frappe.utils import flt, nowdate
from frappe.model.document import Document
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
class OpeningInvoiceCreationTool(Document):
@ -173,6 +174,13 @@ class OpeningInvoiceCreationTool(Document):
"currency": frappe.get_cached_value('Company', self.company, "default_currency")
})
accounting_dimension = get_accounting_dimensions()
for dimension in accounting_dimension:
args.update({
dimension: item.get(dimension)
})
if self.invoice_type == "Sales":
args["is_pos"] = 0

View File

@ -15,7 +15,9 @@
"outstanding_amount",
"column_break_4",
"qty",
"cost_center"
"accounting_dimensions_section",
"cost_center",
"dimension_col_break"
],
"fields": [
{
@ -92,10 +94,19 @@
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
}
],
"istable": 1,
"modified": "2019-06-13 11:48:08.324063",
"modified": "2019-07-25 15:00:00.460695",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Opening Invoice Creation Tool Item",

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();
@ -148,16 +152,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

@ -29,10 +29,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):
@ -135,6 +137,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

@ -764,6 +764,7 @@
"label": "Image"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
@ -782,7 +783,7 @@
],
"idx": 1,
"istable": 1,
"modified": "2019-06-28 17:30:12.156086",
"modified": "2019-07-16 16:36:46.527606",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@ from frappe import _
from frappe.model.document import Document
from frappe.utils.data import nowdate, getdate, cint, add_days, date_diff, get_last_day, add_to_date, flt
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
class Subscription(Document):
@ -241,6 +242,15 @@ class Subscription(Document):
invoice.posting_date = self.current_invoice_start
invoice.customer = self.customer
## Add dimesnions in invoice for subscription:
accounting_dimensions = get_accounting_dimensions()
for dimension in accounting_dimensions:
if self.get(dimension):
invoice.update({
dimension: self.get(dimension)
})
# Subscription is better suited for service items. I won't update `update_stock`
# for that reason
items_list = self.get_items_from_plans(self.plans, prorate)

View File

@ -1,612 +1,163 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 1,
"autoname": "field:plan_name",
"beta": 0,
"creation": "2018-02-24 11:31:23.066506",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"allow_rename": 1,
"autoname": "field:plan_name",
"creation": "2018-02-24 11:31:23.066506",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"plan_name",
"currency",
"column_break_3",
"item",
"section_break_5",
"price_determination",
"column_break_7",
"cost",
"price_list",
"section_break_11",
"billing_interval",
"column_break_13",
"billing_interval_count",
"payment_plan_section",
"payment_plan_id",
"column_break_16",
"payment_gateway",
"accounting_dimensions_section",
"dimension_col_break"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "plan_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Plan Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"fieldname": "plan_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Plan Name",
"reqd": 1,
"unique": 1
},
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "currency",
"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": "Currency",
"length": 0,
"no_copy": 0,
"options": "Currency",
"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,
"translatable": 0,
"unique": 0
},
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
"options": "Currency"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "item",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Item",
"length": 0,
"no_copy": 0,
"options": "Item",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "item",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item",
"options": "Item",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "section_break_5",
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "price_determination",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Price Determination",
"length": 0,
"no_copy": 0,
"options": "\nFixed rate\nBased on price list",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "price_determination",
"fieldtype": "Select",
"label": "Price Determination",
"options": "\nFixed rate\nBased on price list",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_7",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_7",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.price_determination==\"Fixed rate\"",
"fieldname": "cost",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Cost",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"depends_on": "eval:doc.price_determination==\"Fixed rate\"",
"fieldname": "cost",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Cost"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.price_determination==\"Based on price list\"",
"fieldname": "price_list",
"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": "Price List",
"length": 0,
"no_copy": 0,
"options": "Price List",
"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,
"translatable": 0,
"unique": 0
},
"depends_on": "eval:doc.price_determination==\"Based on price list\"",
"fieldname": "price_list",
"fieldtype": "Link",
"label": "Price List",
"options": "Price List"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_11",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "section_break_11",
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Day",
"fieldname": "billing_interval",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Billing Interval",
"length": 0,
"no_copy": 0,
"options": "Day\nWeek\nMonth\nYear",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"default": "Day",
"fieldname": "billing_interval",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Billing Interval",
"options": "Day\nWeek\nMonth\nYear",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_13",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_13",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"description": "Number of intervals for the interval field e.g if Interval is 'Days' and Billing Interval Count is 3, invoices will be generated every 3 days",
"fieldname": "billing_interval_count",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Billing Interval Count",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"default": "1",
"description": "Number of intervals for the interval field e.g if Interval is 'Days' and Billing Interval Count is 3, invoices will be generated every 3 days",
"fieldname": "billing_interval_count",
"fieldtype": "Int",
"label": "Billing Interval Count",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "payment_plan_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payment Plan",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "payment_plan_section",
"fieldtype": "Section Break",
"label": "Payment Plan"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "payment_plan_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payment Plan",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "payment_plan_id",
"fieldtype": "Data",
"label": "Payment Plan"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_16",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_16",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "payment_gateway",
"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": "Payment Gateway",
"length": 0,
"no_copy": 0,
"options": "Payment Gateway Account",
"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,
"translatable": 0,
"unique": 0
"fieldname": "payment_gateway",
"fieldtype": "Link",
"label": "Payment Gateway",
"options": "Payment Gateway Account"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-06-20 16:59:54.082358",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription Plan",
"name_case": "",
"owner": "Administrator",
],
"modified": "2019-07-25 18:35:04.362556",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription Plan",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

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

@ -115,13 +115,12 @@ frappe.query_reports["Accounts Payable"] = {
}
}
erpnext.dimension_filters.then((dimensions) => {
dimensions.forEach((dimension) => {
frappe.query_reports["Accounts Payable"].filters.splice(9, 0 ,{
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
"fieldtype": "Link",
"options": dimension["document_type"]
});
erpnext.dimension_filters.forEach((dimension) => {
frappe.query_reports["Accounts Payable"].filters.splice(9, 0 ,{
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
"fieldtype": "Link",
"options": dimension["document_type"]
});
});

View File

@ -99,13 +99,12 @@ frappe.query_reports["Accounts Payable Summary"] = {
}
}
erpnext.dimension_filters.then((dimensions) => {
dimensions.forEach((dimension) => {
frappe.query_reports["Accounts Payable Summary"].filters.splice(9, 0 ,{
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
"fieldtype": "Link",
"options": dimension["document_type"]
});
erpnext.dimension_filters.forEach((dimension) => {
frappe.query_reports["Accounts Payable Summary"].filters.splice(9, 0 ,{
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
"fieldtype": "Link",
"options": dimension["document_type"]
});
});

View File

@ -173,13 +173,12 @@ frappe.query_reports["Accounts Receivable"] = {
}
}
erpnext.dimension_filters.then((dimensions) => {
dimensions.forEach((dimension) => {
frappe.query_reports["Accounts Receivable"].filters.splice(9, 0 ,{
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
"fieldtype": "Link",
"options": dimension["document_type"]
});
erpnext.dimension_filters.forEach((dimension) => {
frappe.query_reports["Accounts Receivable"].filters.splice(9, 0 ,{
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
"fieldtype": "Link",
"options": dimension["document_type"]
});
});

View File

@ -117,13 +117,11 @@ frappe.query_reports["Accounts Receivable Summary"] = {
}
}
erpnext.dimension_filters.then((dimensions) => {
dimensions.forEach((dimension) => {
frappe.query_reports["Accounts Receivable Summary"].filters.splice(9, 0 ,{
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
"fieldtype": "Link",
"options": dimension["document_type"]
});
erpnext.dimension_filters.forEach((dimension) => {
frappe.query_reports["Accounts Receivable Summary"].filters.splice(9, 0 ,{
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
"fieldtype": "Link",
"options": dimension["document_type"]
});
});

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

@ -63,8 +63,7 @@ frappe.query_reports["Budget Variance Report"] = {
]
}
erpnext.dimension_filters.then((dimensions) => {
dimensions.forEach((dimension) => {
frappe.query_reports["Budget Variance Report"].filters[4].options.push(dimension["document_type"]);
});
erpnext.dimension_filters.forEach((dimension) => {
frappe.query_reports["Budget Variance Report"].filters[4].options.push(dimension["document_type"]);
});

View File

@ -159,13 +159,12 @@ frappe.query_reports["General Ledger"] = {
]
}
erpnext.dimension_filters.then((dimensions) => {
dimensions.forEach((dimension) => {
frappe.query_reports["General Ledger"].filters.splice(15, 0 ,{
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
"fieldtype": "Link",
"options": dimension["document_type"]
});
erpnext.dimension_filters.forEach((dimension) => {
frappe.query_reports["General Ledger"].filters.splice(15, 0 ,{
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
"fieldtype": "Link",
"options": dimension["document_type"]
});
});

View File

@ -105,9 +105,8 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"initial_depth": 3
}
erpnext.dimension_filters.then((dimensions) => {
dimensions.forEach((dimension) => {
frappe.query_reports["Profitability Analysis"].filters[1].options.push(dimension["document_type"]);
});
erpnext.dimension_filters.forEach((dimension) => {
frappe.query_reports["Profitability Analysis"].filters[1].options.push(dimension["document_type"]);
});
});

View File

@ -68,13 +68,12 @@ frappe.query_reports["Sales Register"] = {
]
}
erpnext.dimension_filters.then((dimensions) => {
dimensions.forEach((dimension) => {
frappe.query_reports["Sales Register"].filters.splice(7, 0 ,{
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
"fieldtype": "Link",
"options": dimension["document_type"]
});
erpnext.dimension_filters.forEach((dimension) => {
frappe.query_reports["Sales Register"].filters.splice(7, 0 ,{
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
"fieldtype": "Link",
"options": dimension["document_type"]
});
});

View File

@ -94,10 +94,8 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"parent_field": "parent_account",
"initial_depth": 3
}
});
erpnext.dimension_filters.then((dimensions) => {
dimensions.forEach((dimension) => {
erpnext.dimension_filters.forEach((dimension) => {
frappe.query_reports["Trial Balance"].filters.splice(5, 0 ,{
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
@ -107,3 +105,5 @@ erpnext.dimension_filters.then((dimensions) => {
});
});

View File

@ -123,11 +123,12 @@ def get_rootwise_opening_balances(filters, report_type):
if accounting_dimensions:
for dimension in accounting_dimensions:
additional_conditions += """ and {0} in (%({0})s) """.format(dimension)
if filters.get(dimension):
additional_conditions += """ and {0} in (%({0})s) """.format(dimension)
query_filters.update({
dimension: filters.get(dimension)
})
query_filters.update({
dimension: filters.get(dimension)
})
gle = frappe.db.sql("""
select

View File

@ -30,7 +30,7 @@ class TestProcurementTracker(unittest.TestCase):
company_name="_Test Procurement Company",
abbr="_TPC",
default_currency="INR",
country="India"
country="Pakistan"
)).insert()
warehouse = create_warehouse("_Test Procurement Warehouse", company="_Test Procurement Company")
mr = make_material_request(company="_Test Procurement Company", warehouse=warehouse)

View File

@ -60,7 +60,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)

View File

@ -81,7 +81,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

@ -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

@ -624,4 +624,5 @@ erpnext.patches.v11_1.update_default_supplier_in_item_defaults
erpnext.patches.v12_0.update_due_date_in_gle
erpnext.patches.v12_0.add_default_buying_selling_terms_in_company
erpnext.patches.v12_0.update_ewaybill_field_position
erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes
erpnext.patches.v11_1.set_status_for_material_request_type_manufacture

View File

@ -0,0 +1,41 @@
from __future__ import unicode_literals
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
def execute():
frappe.reload_doc('accounts', 'doctype', 'accounting_dimension')
accounting_dimensions = frappe.db.sql("""select fieldname, label, document_type, disabled from
`tabAccounting Dimension`""", as_dict=1)
if not accounting_dimensions:
return
count = 1
for d in accounting_dimensions:
if count%2 == 0:
insert_after_field = 'dimension_col_break'
else:
insert_after_field = 'accounting_dimensions_section'
for doctype in ["Subscription Plan", "Subscription", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item"]:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
if field:
continue
df = {
"fieldname": d.fieldname,
"label": d.label,
"fieldtype": "Link",
"options": d.document_type,
"insert_after": insert_after_field
}
create_custom_field(doctype, df)
frappe.clear_cache(doctype=doctype)
count += 1

View File

@ -10,18 +10,19 @@ def execute():
field = frappe.db.get_value("Custom Field", {"dt": "Sales Invoice", "fieldname": "ewaybill"})
ewaybill_field = frappe.get_doc("Custom Field", field)
if field:
ewaybill_field = frappe.get_doc("Custom Field", field)
ewaybill_field.flags.ignore_validate = True
ewaybill_field.flags.ignore_validate = True
ewaybill_field.update({
'fieldname': 'ewaybill',
'label': 'e-Way Bill No.',
'fieldtype': 'Data',
'depends_on': 'eval:(doc.docstatus === 1)',
'allow_on_submit': 1,
'insert_after': 'tax_id',
'translatable': 0
})
ewaybill_field.update({
'fieldname': 'ewaybill',
'label': 'e-Way Bill No.',
'fieldtype': 'Data',
'depends_on': 'eval:(doc.docstatus === 1)',
'allow_on_submit': 1,
'insert_after': 'tax_id',
'translatable': 0
})
ewaybill_field.save()
ewaybill_field.save()

View File

@ -145,7 +145,7 @@ class Task(NestedSet):
def populate_depends_on(self):
if self.parent_task:
parent = frappe.get_cached_doc('Task', self.parent_task)
parent = frappe.get_doc('Task', self.parent_task)
if not self.name in [row.task for row in parent.depends_on]:
parent.append("depends_on", {
"doctype": "Task Depends On",

View File

@ -92,7 +92,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

@ -129,14 +129,12 @@ function get_filters(){
}
]
erpnext.dimension_filters.then((dimensions) => {
dimensions.forEach((dimension) => {
filters.push({
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
"fieldtype": "Link",
"options": dimension["document_type"]
});
erpnext.dimension_filters.forEach((dimension) => {
filters.push({
"fieldname": dimension["fieldname"],
"label": __(dimension["label"]),
"fieldtype": "Link",
"options": dimension["document_type"]
});
});

View File

@ -63,20 +63,6 @@ $.extend(erpnext, {
me.show_serial_batch_selector(grid_row.frm, grid_row.doc);
});
},
get_dimension_filters: async function() {
if (!frappe.model.can_read('Accounting Dimension')) {
return [];
}
let dimensions = await frappe.db.get_list('Accounting Dimension', {
fields: ['label', 'fieldname', 'document_type'],
filters: {
disabled: 0
}
});
return dimensions;
}
});

View File

@ -5,24 +5,54 @@ erpnext.doctypes_with_dimensions = ["GL Entry", "Sales Invoice", "Purchase Invoi
"Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item",
"Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
"Travel Request", "Fees", "POS Profile"];
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
"Subscription Plan"];
erpnext.dimension_filters = erpnext.get_dimension_filters();
frappe.call({
method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.get_dimension_filters",
callback: function(r){
erpnext.dimension_filters = r.message[0];
erpnext.default_dimensions = r.message[1];
}
});
erpnext.doctypes_with_dimensions.forEach((doctype) => {
frappe.ui.form.on(doctype, {
onload: function(frm) {
erpnext.dimension_filters.then((dimensions) => {
dimensions.forEach((dimension) => {
frappe.model.with_doctype(dimension['document_type'], () => {
if (frappe.meta.has_field(dimension['document_type'], 'is_group')) {
frm.set_query(dimension['fieldname'], {
"is_group": 0
});
}
});
erpnext.dimension_filters.forEach((dimension) => {
frappe.model.with_doctype(dimension['document_type'], () => {
if (frappe.meta.has_field(dimension['document_type'], 'is_group')) {
frm.set_query(dimension['fieldname'], {
"is_group": 0
});
}
if (frm.is_new() && frappe.meta.has_field(doctype, 'company') && frm.doc.company) {
frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]);
}
});
});
},
company: function(frm) {
if(frm.doc.company) {
erpnext.dimension_filters.forEach((dimension) => {
frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]);
});
}
},
items_add: function(frm, cdt, cdn) {
erpnext.dimension_filters.forEach((dimension) => {
var row = frappe.get_doc(cdt, cdn);
frm.script_manager.copy_from_first_row("items", row, [dimension['fieldname']]);
});
},
accounts_add: function(frm, cdt, cdn) {
erpnext.dimension_filters.forEach((dimension) => {
var row = frappe.get_doc(cdt, cdn);
frm.script_manager.copy_from_first_row("accounts", row, [dimension['fieldname']]);
});
}
});
});

View File

@ -161,8 +161,9 @@ class Gstr1Report(object):
"gst_category": ["in", ["Registered Regular", "Deemed Export", "SEZ"]]
})
conditions += """ and ifnull(gst_category, '') != 'Overseas' and is_return != 1
and customer in ({0})""".format(", ".join([frappe.db.escape(c.name) for c in customers]))
if customers:
conditions += """ and ifnull(gst_category, '') != 'Overseas' and is_return != 1
and customer in ({0})""".format(", ".join([frappe.db.escape(c.name) for c in customers]))
if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"):
b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit')
@ -174,11 +175,11 @@ class Gstr1Report(object):
"gst_category": ["in", ["Unregistered"]]
})
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})""".\
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})""".\

View File

@ -255,27 +255,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

@ -0,0 +1,27 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Customer-wise Item Price"] = {
"filters": [
{
"label": __("Customer"),
"fieldname": "customer",
"fieldtype": "Link",
"options": "Customer",
"reqd": 1
},
{
"label": __("Item"),
"fieldname": "item",
"fieldtype": "Link",
"options": "Item",
"get_query": () => {
return {
query: "erpnext.controllers.queries.item_query",
filters: { 'is_sales_item': 1 }
}
}
}
]
}

View File

@ -0,0 +1,43 @@
{
"add_total_row": 0,
"creation": "2019-06-12 03:25:36.263179",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"letter_head": "Delta9",
"modified": "2019-06-12 03:25:36.263179",
"modified_by": "Administrator",
"module": "Selling",
"name": "Customer-wise Item Price",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Customer",
"report_name": "Customer-wise Item Price",
"report_type": "Script Report",
"roles": [
{
"role": "Sales User"
},
{
"role": "Stock Manager"
},
{
"role": "Accounts User"
},
{
"role": "Accounts Manager"
},
{
"role": "Sales Manager"
},
{
"role": "Sales Master Manager"
},
{
"role": "Stock User"
}
]
}

View File

@ -0,0 +1,103 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from erpnext import get_default_company
from erpnext.accounts.party import get_party_details
from erpnext.stock.get_item_details import get_price_list_rate_for
from frappe import _
def execute(filters=None):
if not filters:
filters = {}
if not filters.get("customer"):
frappe.throw(_("Please select a Customer"))
columns = get_columns(filters)
data = get_data(filters)
return columns, data
def get_columns(filters=None):
return [
{
"label": _("Item Code"),
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
"width": 150
},
{
"label": _("Item Name"),
"fieldname": "item_name",
"fieldtype": "Data",
"width": 200
},
{
"label": _("Selling Rate"),
"fieldname": "selling_rate",
"fieldtype": "Currency"
},
{
"label": _("Available Stock"),
"fieldname": "available_stock",
"fieldtype": "Float",
"width": 150
},
{
"label": _("Price List"),
"fieldname": "price_list",
"fieldtype": "Link",
"options": "Price List",
"width": 120
}
]
def get_data(filters=None):
data = []
customer_details = get_customer_details(filters)
items = get_selling_items(filters)
item_stock_map = frappe.get_all("Bin", fields=["item_code", "sum(actual_qty) AS available"], group_by="item_code")
item_stock_map = {item.item_code: item.available for item in item_stock_map}
for item in items:
price_list_rate = get_price_list_rate_for(customer_details, item.item_code) or 0.0
available_stock = item_stock_map.get(item.item_code)
data.append({
"item_code": item.item_code,
"item_name": item.item_name,
"selling_rate": price_list_rate,
"price_list": customer_details.get("price_list"),
"available_stock": available_stock,
})
return data
def get_customer_details(filters):
customer_details = get_party_details(party=filters.get("customer"), party_type="Customer")
customer_details.update({
"company": get_default_company(),
"price_list": customer_details.get("selling_price_list")
})
return customer_details
def get_selling_items(filters):
if filters.get("item"):
item_filters = {"item_code": filters.get("item"), "is_sales_item": 1, "disabled": 0}
else:
item_filters = {"is_sales_item": 1, "disabled": 0}
items = frappe.get_all("Item", filters=item_filters, fields=["item_code", "item_name"], order_by="item_name")
return items

View File

@ -802,13 +802,13 @@ class StockEntry(StockController):
self.add_to_stock_entry_detail(item_dict)
if self.purpose != "Send to Subcontractor":
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 != "Send to Subcontractor" 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

@ -327,7 +327,8 @@ class StockReconciliation(StockController):
if sl_entries:
sl_entries.reverse()
self.make_sl_entries(sl_entries)
allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock)
# repost future entries for selected item_code, warehouse
for entries in existing_entries:

View File

@ -17,10 +17,11 @@ from erpnext.stock.utils import get_stock_balance, get_incoming_rate, get_availa
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
class TestStockReconciliation(unittest.TestCase):
def setUp(self):
@classmethod
def setUpClass(self):
create_batch_or_serial_no_items()
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")
@ -97,18 +98,6 @@ 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
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="2013-01-05", posting_time="07:00", item_code="_Test Item",
target="_Test Warehouse - _TC", qty=15, basic_rate=1200)
def test_stock_reco_for_serialized_item(self):
set_perpetual_inventory()
@ -218,6 +207,18 @@ class TestStockReconciliation(unittest.TestCase):
for d in to_delete_records:
frappe.delete_doc("Stock Reconciliation", d)
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-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)
def create_batch_or_serial_no_items():
create_warehouse("_Test Warehouse for Stock Reco1",
{"is_group": 0, "parent_warehouse": "_Test Warehouse Group - _TC"})

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

@ -37,8 +37,8 @@ frappe.query_reports["Stock Ageing"] = {
"options": "Brand"
},
{
"fieldname":"show_ageing_warehouse_wise",
"label": __("Show Ageing Warehouse-wise"),
"fieldname":"show_warehouse_wise_stock",
"label": __("Show Warehouse-wise Stock"),
"fieldtype": "Check",
"default": 0
}

View File

@ -6,6 +6,7 @@ import frappe
from frappe import _
from frappe.utils import date_diff, flt
from six import iteritems
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
def execute(filters=None):
@ -14,9 +15,9 @@ def execute(filters=None):
to_date = filters["to_date"]
data = []
for item, item_dict in iteritems(item_details):
fifo_queue = item_dict["fifo_queue"]
fifo_queue = sorted(item_dict["fifo_queue"], key=lambda x: x[1])
details = item_dict["details"]
if not fifo_queue: continue
if not fifo_queue or (not item_dict.get("total_qty")): continue
average_age = get_average_age(fifo_queue, to_date)
earliest_age = date_diff(to_date, fifo_queue[0][1])
@ -25,7 +26,7 @@ def execute(filters=None):
row = [details.name, details.item_name,
details.description, details.item_group, details.brand]
if filters.get("show_ageing_warehouse_wise"):
if filters.get("show_warehouse_wise_stock"):
row.append(details.warehouse)
row.extend([item_dict.get("total_qty"), average_age,
@ -39,48 +40,150 @@ def get_average_age(fifo_queue, to_date):
batch_age = age_qty = total_qty = 0.0
for batch in fifo_queue:
batch_age = date_diff(to_date, batch[1])
age_qty += batch_age * batch[0]
total_qty += batch[0]
if type(batch[0]) in ['int', 'float']:
age_qty += batch_age * batch[0]
total_qty += batch[0]
else:
age_qty += batch_age * 1
total_qty += 1
return (age_qty / total_qty) if total_qty else 0.0
def get_columns(filters):
columns = [_("Item Code") + ":Link/Item:100", _("Item Name") + "::100", _("Description") + "::200",
_("Item Group") + ":Link/Item Group:100", _("Brand") + ":Link/Brand:100"]
columns = [
{
"label": _("Item Code"),
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
"width": 100
},
{
"label": _("Item Name"),
"fieldname": "item_name",
"fieldtype": "Data",
"width": 100
},
{
"label": _("Description"),
"fieldname": "description",
"fieldtype": "Data",
"width": 200
},
{
"label": _("Item Group"),
"fieldname": "item_group",
"fieldtype": "Link",
"options": "Item Group",
"width": 100
},
{
"label": _("Brand"),
"fieldname": "brand",
"fieldtype": "Link",
"options": "Brand",
"width": 100
}]
if filters.get("show_ageing_warehouse_wise"):
columns.extend([_("Warehouse") + ":Link/Warehouse:100"])
if filters.get("show_warehouse_wise_stock"):
columns +=[{
"label": _("Warehouse"),
"fieldname": "warehouse",
"fieldtype": "Link",
"options": "Warehouse",
"width": 100
}]
columns.extend([_("Available Qty") + ":Float:100", _("Average Age") + ":Float:100",
_("Earliest") + ":Int:80", _("Latest") + ":Int:80", _("UOM") + ":Link/UOM:100"])
columns.extend([
{
"label": _("Available Qty"),
"fieldname": "qty",
"fieldtype": "Float",
"width": 100
},
{
"label": _("Average Age"),
"fieldname": "average_age",
"fieldtype": "Float",
"width": 100
},
{
"label": _("Earliest"),
"fieldname": "earliest",
"fieldtype": "Int",
"width": 80
},
{
"label": _("Latest"),
"fieldname": "latest",
"fieldtype": "Int",
"width": 80
},
{
"label": _("UOM"),
"fieldname": "uom",
"fieldtype": "Link",
"options": "UOM",
"width": 100
}
])
return columns
def get_fifo_queue(filters):
item_details = {}
for d in get_stock_ledger_entries(filters):
key = (d.name, d.warehouse) if filters.get('show_ageing_warehouse_wise') else d.name
transfered_item_details = {}
serial_no_batch_purchase_details = {}
sle = get_stock_ledger_entries(filters)
for d in sle:
key = (d.name, d.warehouse) if filters.get('show_warehouse_wise_stock') else d.name
item_details.setdefault(key, {"details": d, "fifo_queue": []})
fifo_queue = item_details[key]["fifo_queue"]
transfered_item_details.setdefault((d.voucher_no, d.name), [])
if d.voucher_type == "Stock Reconciliation":
d.actual_qty = flt(d.qty_after_transaction) - flt(item_details[key].get("qty_after_transaction", 0))
serial_no_list = get_serial_nos(d.serial_no) if d.serial_no else []
if d.actual_qty > 0:
fifo_queue.append([d.actual_qty, d.posting_date])
else:
qty_to_pop = abs(d.actual_qty)
while qty_to_pop:
batch = fifo_queue[0] if fifo_queue else [0, None]
if 0 < batch[0] <= qty_to_pop:
# if batch qty > 0
# not enough or exactly same qty in current batch, clear batch
qty_to_pop -= batch[0]
fifo_queue.pop(0)
if transfered_item_details.get((d.voucher_no, d.name)):
batch = transfered_item_details[(d.voucher_no, d.name)][0]
fifo_queue.append(batch)
transfered_item_details[((d.voucher_no, d.name))].pop(0)
else:
if serial_no_list:
for serial_no in serial_no_list:
if serial_no_batch_purchase_details.get(serial_no):
fifo_queue.append([serial_no, serial_no_batch_purchase_details.get(serial_no)])
else:
serial_no_batch_purchase_details.setdefault(serial_no, d.posting_date)
fifo_queue.append([serial_no, d.posting_date])
else:
# all from current batch
batch[0] -= qty_to_pop
qty_to_pop = 0
fifo_queue.append([d.actual_qty, d.posting_date])
else:
if serial_no_list:
for serial_no in fifo_queue:
if serial_no[0] in serial_no_list:
fifo_queue.remove(serial_no)
else:
qty_to_pop = abs(d.actual_qty)
while qty_to_pop:
batch = fifo_queue[0] if fifo_queue else [0, None]
if 0 < batch[0] <= qty_to_pop:
# if batch qty > 0
# not enough or exactly same qty in current batch, clear batch
qty_to_pop -= batch[0]
transfered_item_details[(d.voucher_no, d.name)].append(fifo_queue.pop(0))
else:
# all from current batch
batch[0] -= qty_to_pop
transfered_item_details[(d.voucher_no, d.name)].append([qty_to_pop, batch[1]])
qty_to_pop = 0
item_details[key]["qty_after_transaction"] = d.qty_after_transaction
@ -94,7 +197,7 @@ def get_fifo_queue(filters):
def get_stock_ledger_entries(filters):
return frappe.db.sql("""select
item.name, item.item_name, item_group, brand, description, item.stock_uom,
actual_qty, posting_date, voucher_type, qty_after_transaction, warehouse
actual_qty, posting_date, voucher_type, voucher_no, serial_no, batch_no, qty_after_transaction, warehouse
from `tabStock Ledger Entry` sle,
(select name, item_name, description, stock_uom, brand, item_group
from `tabItem` {item_conditions}) item
@ -102,7 +205,7 @@ def get_stock_ledger_entries(filters):
company = %(company)s and
posting_date <= %(to_date)s
{sle_conditions}
order by posting_date, posting_time, sle.creation"""\
order by posting_date, posting_time, sle.creation, actual_qty""" #nosec
.format(item_conditions=get_item_conditions(filters),
sle_conditions=get_sle_conditions(filters)), filters, as_dict=True)

View File

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

@ -210,6 +210,7 @@ class Issue(Document):
self.service_level_agreement_creation = now_datetime()
self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
self.agreement_fulfilled = "Ongoing"
self.save()
def get_expected_time_for(parameter, service_level, start_date_time):

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