Merge branch 'develop' into healthcare_refactor

This commit is contained in:
Nabin Hait 2020-04-08 09:08:21 +05:30 committed by GitHub
commit b95a794e51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 331 additions and 552 deletions

View File

@ -663,4 +663,5 @@ erpnext.patches.v12_0.move_bank_account_swift_number_to_bank
erpnext.patches.v12_0.rename_bank_reconciliation_fields # 2020-01-22
erpnext.patches.v12_0.set_received_qty_in_material_request_as_per_stock_uom
erpnext.patches.v12_0.recalculate_requested_qty_in_bin
erpnext.patches.v12_0.update_healthcare_refactored_changes
erpnext.patches.v12_0.update_healthcare_refactored_changes
erpnext.patches.v12_0.set_total_batch_quantity

View File

@ -3,10 +3,10 @@
from __future__ import unicode_literals
import frappe
from erpnext.regional.india.setup import update_address_template
from erpnext.regional.address_template.setup import set_up_address_templates
def execute():
if frappe.db.get_value('Company', {'country': 'India'}, 'name'):
address_template = frappe.db.get_value('Address Template', 'India', 'template')
if not address_template or "gstin" not in address_template:
update_address_template()
set_up_address_templates(default_country='India')

View File

@ -0,0 +1,11 @@
import frappe
def execute():
frappe.reload_doc("stock", "doctype", "batch")
for batch in frappe.get_all("Batch", fields=["name", "batch_id"]):
batch_qty = frappe.db.get_value("Stock Ledger Entry",
{"docstatus": 1, "batch_no": batch.batch_id, "is_cancelled": "No"},
"sum(actual_qty)") or 0.0
frappe.db.set_value("Batch", batch.name, "batch_qty", batch_qty, update_modified=False)

View File

@ -1,7 +1,8 @@
from __future__ import unicode_literals
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from erpnext.regional.india.setup import update_address_template
from erpnext.regional.address_template.setup import set_up_address_templates
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
@ -10,9 +11,10 @@ def execute():
update_existing_custom_fields()
add_custom_fields()
update_address_template()
set_up_address_templates(default_country='India')
frappe.reload_doc("regional", "print_format", "gst_tax_invoice")
def update_existing_custom_fields():
frappe.db.sql("""update `tabCustom Field` set label = 'HSN/SAC'
where fieldname='gst_hsn_code' and label='GST HSN Code'
@ -34,6 +36,7 @@ def update_existing_custom_fields():
where fieldname='gst_hsn_code' and dt in ('Sales Invoice Item', 'Purchase Invoice Item')
""")
def add_custom_fields():
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
fieldtype='Data', options='item_code.gst_hsn_code', insert_after='description')

View File

@ -0,0 +1,18 @@
To add an **Address Template** for your country, place a new file in this directory:
* File name: `your_country.html` (lower case with underscores)
* File content: a [Jinja Template](http://jinja.pocoo.org/docs/templates/).
All the fields of **Address** (including Custom Fields, if any) will be available to the template. Example:
```jinja
{{ address_line1 }}<br>
{% if address_line2 %}{{ address_line2 }}<br>{% endif -%}
{{ city }}<br>
{% if state %}{{ state }}<br>{% endif -%}
{% if pincode %} PIN: {{ pincode }}<br>{% endif -%}
{{ country }}<br>
{% if phone %}Phone: {{ phone }}<br>{% endif -%}
{% if fax %}Fax: {{ fax }}<br>{% endif -%}
{% if email_id %}Email: {{ email_id }}<br>{% endif -%}
```

View File

@ -0,0 +1,53 @@
"""Import Address Templates from ./templates directory."""
import os
import frappe
def set_up_address_templates(default_country=None):
for country, html in get_address_templates():
is_default = 1 if country == default_country else 0
update_address_template(country, html, is_default)
def get_address_templates():
"""
Return country and path for all HTML files in this directory.
Returns a list of dicts.
"""
def country(file_name):
"""Convert 'united_states.html' to 'United States'."""
suffix_pos = file_name.find(".html")
country_snake_case = file_name[:suffix_pos]
country_title_case = " ".join(country_snake_case.split("_")).title()
return country_title_case
def get_file_content(file_name):
"""Convert 'united_states.html' to '/path/to/united_states.html'."""
full_path = os.path.join(template_dir, file_name)
with open(full_path, "r") as f:
content = f.read()
return content
dir_name = os.path.dirname(__file__)
template_dir = os.path.join(dir_name, "templates")
file_names = os.listdir(template_dir)
html_files = [file for file in file_names if file.endswith(".html")]
return [(country(file_name), get_file_content(file_name)) for file_name in html_files]
def update_address_template(country, html, is_default=0):
"""Update existing Address Template or create a new one."""
if not frappe.db.exists("Country", country):
frappe.log_error("Country {} for regional Address Template does not exist.".format(country))
return
if frappe.db.exists("Address Template", country):
frappe.db.set_value("Address Template", country, "template", html)
frappe.db.set_value("Address Template", country, "is_default", is_default)
else:
frappe.get_doc(dict(
doctype="Address Template",
country=country,
is_default=is_default,
template=html
)).insert()

View File

@ -6,4 +6,4 @@
{% if phone %}Phone: {{ phone }}<br>{% endif -%}
{% if fax %}Fax: {{ fax }}<br>{% endif -%}
{% if email_id %}Email: {{ email_id }}<br>{% endif -%}
{% if gstin %}GSTIN: {{ gstin }}<br>{% endif -%}
{% if gstin %}GSTIN: {{ gstin }}<br>{% endif -%}

View File

@ -0,0 +1,45 @@
from __future__ import unicode_literals
from unittest import TestCase
import frappe
from erpnext.regional.address_template.setup import get_address_templates
from erpnext.regional.address_template.setup import update_address_template
def ensure_country(country):
if frappe.db.exists("Country", country):
return frappe.get_doc("Country", country)
else:
c = frappe.get_doc({
"doctype": "Country",
"country_name": country
})
c.insert()
return c
class TestRegionalAddressTemplate(TestCase):
def test_get_address_templates(self):
"""Get the countries and paths from the templates directory."""
templates = get_address_templates()
self.assertIsInstance(templates, list)
self.assertIsInstance(templates[0], tuple)
def test_create_address_template(self):
"""Create a new Address Template."""
country = ensure_country("Germany")
update_address_template(country.name, "TEST")
doc = frappe.get_doc("Address Template", country.name)
self.assertEqual(doc.template, "TEST")
def test_update_address_template(self):
"""Update an existing Address Template."""
country = ensure_country("Germany")
if not frappe.db.exists("Address Template", country.name):
template = frappe.get_doc({
"doctype": "Address Template",
"country": country.name,
"template": "EXISTING"
}).insert()
update_address_template(country.name, "NEW")
doc = frappe.get_doc("Address Template", country.name)
self.assertEqual(doc.template, "NEW")

View File

@ -3,29 +3,4 @@ import frappe
def setup(company=None, patch=True):
if not patch:
update_address_template()
def update_address_template():
"""
Read address template from file. Update existing Address Template or create a
new one.
"""
dir_name = os.path.dirname(__file__)
template_path = os.path.join(dir_name, 'address_template.html')
with open(template_path, 'r') as template_file:
template_html = template_file.read()
address_template = frappe.db.get_value('Address Template', 'Germany')
if address_template:
frappe.db.set_value('Address Template', 'Germany', 'template', template_html)
else:
# make new html template for Germany
frappe.get_doc(dict(
doctype='Address Template',
country='Germany',
template=template_html
)).insert()
pass

View File

@ -13,7 +13,6 @@ from frappe.utils import today
def setup(company=None, patch=True):
setup_company_independent_fixtures()
if not patch:
update_address_template()
make_fixtures(company)
# TODO: for all countries
@ -24,21 +23,6 @@ def setup_company_independent_fixtures():
frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test)
add_print_formats()
def update_address_template():
with open(os.path.join(os.path.dirname(__file__), 'address_template.html'), 'r') as f:
html = f.read()
address_template = frappe.db.get_value('Address Template', 'India')
if address_template:
frappe.db.set_value('Address Template', 'India', 'template', html)
else:
# make new html template for India
frappe.get_doc(dict(
doctype='Address Template',
country='India',
template=html
)).insert()
def add_hsn_sac_codes():
# HSN codes
with open(os.path.join(os.path.dirname(__file__), 'hsn_code_data.json'), 'r') as f:

View File

@ -5,12 +5,9 @@ from __future__ import unicode_literals
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def setup(company=None, patch=True):
make_custom_fields()
add_print_formats()
update_address_template()
def make_custom_fields():
custom_fields = {
@ -21,22 +18,7 @@ def make_custom_fields():
}
create_custom_fields(custom_fields)
def add_print_formats():
frappe.reload_doc("regional", "print_format", "irs_1099_form")
frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where
name in('IRS 1099 Form') """)
def update_address_template():
html = """{{ address_line1 }}<br>
{% if address_line2 %}{{ address_line2 }}<br>{% endif -%}
{{ city }}, {% if state %}{{ state }}{% endif -%}{% if pincode %} {{ pincode }}<br>{% endif -%}
{% if country != "United States" %}{{ country|upper }}{% endif -%}
"""
address_template = frappe.db.get_value('Address Template', 'United States')
if address_template:
frappe.db.set_value('Address Template', 'United States', 'template', html)
else:
frappe.get_doc(dict(doctype='Address Template', country='United States', template=html)).insert()

View File

@ -8,9 +8,11 @@ import frappe, os, json
from frappe import _
from frappe.desk.page.setup_wizard.setup_wizard import make_records
from frappe.utils import cstr, getdate
from erpnext.accounts.doctype.account.account import RootNotEditable
from frappe.desk.doctype.global_search_settings.global_search_settings import update_global_search_doctypes
from erpnext.accounts.doctype.account.account import RootNotEditable
from erpnext.regional.address_template.setup import set_up_address_templates
default_lead_sources = ["Existing Customer", "Reference", "Advertisement",
"Cold Calling", "Exhibition", "Supplier Reference", "Mass Mailing",
"Customer's Vendor", "Campaign", "Walk In"]
@ -30,7 +32,7 @@ def install(country=None):
{ 'doctype': 'Domain', 'domain': 'Agriculture'},
{ 'doctype': 'Domain', 'domain': 'Non Profit'},
# address template
# ensure at least an empty Address Template exists for this Country
{'doctype':"Address Template", "country": country},
# item group
@ -269,12 +271,11 @@ def install(country=None):
# Records for the Supplier Scorecard
from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import make_default_records
make_default_records()
make_records(records)
set_up_address_templates(default_country=country)
set_more_defaults()
update_global_search_doctypes()
# path = frappe.get_app_path('erpnext', 'regional', frappe.scrub(country))

View File

@ -1,554 +1,194 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "field:batch_id",
"beta": 0,
"creation": "2013-03-05 14:50:38",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 0,
"engine": "InnoDB",
"field_order": [
"sb_disabled",
"disabled",
"sb_batch",
"batch_id",
"item",
"item_name",
"image",
"parent_batch",
"manufacturing_date",
"column_break_3",
"batch_qty",
"stock_uom",
"expiry_date",
"source",
"supplier",
"column_break_9",
"reference_doctype",
"reference_name",
"section_break_7",
"description"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"depends_on": "eval:doc.__islocal",
"fieldname": "batch_id",
"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": "Batch ID",
"length": 0,
"no_copy": 1,
"oldfieldname": "batch_id",
"oldfieldtype": "Data",
"permlevel": 0,
"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": 1
},
{
"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": 1,
"label": "Item",
"length": 0,
"no_copy": 0,
"oldfieldname": "item",
"oldfieldtype": "Link",
"options": "Item",
"permlevel": 0,
"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
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "image",
"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
"label": "image"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.parent_batch",
"fieldname": "parent_batch",
"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": "Parent Batch",
"length": 0,
"no_copy": 0,
"options": "Batch",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"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
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "disabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Disabled",
"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
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Today",
"fieldname": "manufacturing_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Manufacturing Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"label": "Manufacturing Date"
},
{
"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": "expiry_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Expiry Date",
"length": 0,
"no_copy": 0,
"oldfieldname": "expiry_date",
"oldfieldtype": "Date",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"oldfieldtype": "Date"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "source",
"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": "Source",
"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
"label": "Source"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "supplier",
"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": "Supplier",
"length": 0,
"no_copy": 0,
"options": "Supplier",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9",
"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
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_doctype",
"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": "Source Document Type",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_name",
"fieldtype": "Dynamic 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": "Source Document Name",
"length": 0,
"no_copy": 0,
"options": "reference_doctype",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_7",
"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
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Batch Description",
"length": 0,
"no_copy": 0,
"oldfieldname": "description",
"oldfieldtype": "Small Text",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "300px"
},
{
"fieldname": "sb_disabled",
"fieldtype": "Section Break"
},
{
"fieldname": "sb_batch",
"fieldtype": "Section Break",
"label": "Batch Details"
},
{
"fetch_from": "item.item_name",
"fieldname": "item_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Item Name",
"read_only": 1
},
{
"fieldname": "batch_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Batch Quantity",
"read_only": 1
},
{
"fetch_from": "item.stock_uom",
"fieldname": "stock_uom",
"fieldtype": "Link",
"label": "Batch UOM",
"options": "UOM",
"read_only": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-archive",
"idx": 1,
"image_field": "image",
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 5,
"modified": "2018-08-29 06:28:57.985997",
"modified": "2019-10-03 22:38:45.104056",
"modified_by": "Administrator",
"module": "Stock",
"name": "Batch",
"owner": "harshada@webnotestech.com",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Item Manager",
"set_user_permissions": 1,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "item",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
"title_field": "batch_id"
}

View File

@ -0,0 +1,27 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'batch_no',
'transactions': [
{
'label': _('Buy'),
'items': ['Purchase Invoice', 'Purchase Receipt']
},
{
'label': _('Sell'),
'items': ['Sales Invoice', 'Delivery Note']
},
{
'label': _('Move'),
'items': ['Stock Entry']
},
{
'label': _('Quality'),
'items': ['Quality Inspection']
}
]
}

View File

@ -1,12 +1,14 @@
frappe.listview_settings['Batch'] = {
add_fields: ["item", "expiry_date"],
get_indicator: function(doc) {
if(doc.expiry_date && frappe.datetime.get_diff(doc.expiry_date, frappe.datetime.nowdate()) <= 0) {
return [__("Expired"), "red", "expiry_date,>=,Today"]
} else if(doc.expiry_date) {
return [__("Not Expired"), "green", "expiry_date,<,Today"]
add_fields: ["item", "expiry_date", "batch_qty", "disabled"],
get_indicator: (doc) => {
if (doc.disabled) {
return [__("Disabled"), "darkgrey", "disabled,=,1"];
} else if (!doc.batch_qty) {
return [__("Empty"), "darkgrey", "batch_qty,=,0|disabled,=,0"];
} else if (doc.expiry_date && frappe.datetime.get_diff(doc.expiry_date, frappe.datetime.nowdate()) <= 0) {
return [__("Expired"), "red", "expiry_date,not in,|expiry_date,<=,Today|batch_qty,>,0|disabled,=,0"]
} else {
return ["Not Set", "darkgrey", ""];
}
return [__("Active"), "green", "batch_qty,>,0|disabled,=,0"];
};
}
};

View File

@ -7,7 +7,7 @@ from frappe.exceptions import ValidationError
import unittest
from erpnext.stock.doctype.batch.batch import get_batch_qty, UnableToSelectBatchError, get_batch_no
from frappe.utils import cint
from frappe.utils import cint, flt
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
class TestBatch(unittest.TestCase):
@ -35,12 +35,13 @@ class TestBatch(unittest.TestCase):
receipt = frappe.get_doc(dict(
doctype='Purchase Receipt',
supplier='_Test Supplier',
company='_Test Company',
items=[
dict(
item_code='ITEM-BATCH-1',
qty=batch_qty,
rate=10,
warehouse= 'Stores - WP'
warehouse= 'Stores - _TC'
)
]
)).insert()
@ -175,6 +176,18 @@ class TestBatch(unittest.TestCase):
self.assertEqual(get_batch_qty('batch a', '_Test Warehouse - _TC'), 90)
def test_total_batch_qty(self):
self.make_batch_item('ITEM-BATCH-3')
existing_batch_qty = flt(frappe.db.get_value("Batch", "B100", "batch_qty"))
stock_entry = self.make_new_batch_and_entry('ITEM-BATCH-3', 'B100', '_Test Warehouse - _TC')
current_batch_qty = flt(frappe.db.get_value("Batch", "B100", "batch_qty"))
self.assertEqual(current_batch_qty, existing_batch_qty + 90)
stock_entry.cancel()
current_batch_qty = flt(frappe.db.get_value("Batch", "B100", "batch_qty"))
self.assertEqual(current_batch_qty, existing_batch_qty)
@classmethod
def make_new_batch_and_entry(cls, item_name, batch_name, warehouse):
'''Make a new stock entry for given target warehouse and batch name of item'''
@ -208,6 +221,8 @@ class TestBatch(unittest.TestCase):
stock_entry.insert()
stock_entry.submit()
return stock_entry
def test_batch_name_with_naming_series(self):
stock_settings = frappe.get_single('Stock Settings')
use_naming_series = cint(stock_settings.use_naming_series)

View File

@ -37,11 +37,19 @@ class StockLedgerEntry(Document):
def on_submit(self):
self.check_stock_frozen_date()
self.actual_amt_check()
self.calculate_batch_qty()
if not self.get("via_landed_cost_voucher"):
from erpnext.stock.doctype.serial_no.serial_no import process_serial_no
process_serial_no(self)
def calculate_batch_qty(self):
if self.batch_no:
batch_qty = frappe.db.get_value("Stock Ledger Entry",
{"docstatus": 1, "batch_no": self.batch_no, "is_cancelled": "No"},
"sum(actual_qty)") or 0
frappe.db.set_value("Batch", self.batch_no, "batch_qty", batch_qty)
#check for item quantity available in stock
def actual_amt_check(self):
if self.batch_no and not self.get("allow_negative_stock"):
@ -139,4 +147,3 @@ def on_doctype_update():
frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"])
frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"])

View File

@ -488,12 +488,14 @@ def get_stock_balance_for(item_code, warehouse,
["has_serial_no", "has_batch_no"], as_dict=1)
serial_nos = ""
if item_dict.get("has_serial_no"):
qty, rate, serial_nos = get_qty_rate_for_serial_nos(item_code,
warehouse, posting_date, posting_time, item_dict)
with_serial_no = True if item_dict.get("has_serial_no") else False
data = get_stock_balance(item_code, warehouse, posting_date, posting_time,
with_valuation_rate=with_valuation_rate, with_serial_no=with_serial_no)
if with_serial_no:
qty, rate, serial_nos = data
else:
qty, rate = get_stock_balance(item_code, warehouse,
posting_date, posting_time, with_valuation_rate=with_valuation_rate)
qty, rate = data
if item_dict.get("has_batch_no"):
qty = get_batch_qty(batch_no, warehouse) or 0
@ -504,28 +506,6 @@ def get_stock_balance_for(item_code, warehouse,
'serial_nos': serial_nos
}
def get_qty_rate_for_serial_nos(item_code, warehouse, posting_date, posting_time, item_dict):
args = {
"item_code": item_code,
"warehouse": warehouse,
"posting_date": posting_date,
"posting_time": posting_time,
}
serial_nos_list = [serial_no.get("name")
for serial_no in get_available_serial_nos(args)]
qty = len(serial_nos_list)
serial_nos = '\n'.join(serial_nos_list)
args.update({
'qty': qty,
"serial_nos": serial_nos
})
rate = get_incoming_rate(args, raise_error_if_no_rate=False) or 0
return qty, rate, serial_nos
@frappe.whitelist()
def get_difference_account(purpose, company):
if purpose == 'Stock Reconciliation':

View File

@ -35,7 +35,7 @@ def get_data(report_filters):
gl_data = voucher_wise_gl_data.get(key) or {}
d.account_value = gl_data.get("account_value", 0)
d.difference_value = (d.stock_value - d.account_value)
if abs(d.difference_value) > 1.0/10 ** currency_precision:
if abs(d.difference_value) > 0.1:
data.append(d)
return data

View File

@ -74,7 +74,8 @@ def get_stock_value_on(warehouse=None, posting_date=None, item_code=None):
return sum(sle_map.values())
@frappe.whitelist()
def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None, with_valuation_rate=False):
def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None,
with_valuation_rate=False, with_serial_no=False):
"""Returns stock balance quantity at given warehouse on given posting date or current date.
If `with_valuation_rate` is True, will return tuple (qty, rate)"""
@ -84,17 +85,51 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None
if not posting_date: posting_date = nowdate()
if not posting_time: posting_time = nowtime()
last_entry = get_previous_sle({
args = {
"item_code": item_code,
"warehouse":warehouse,
"posting_date": posting_date,
"posting_time": posting_time })
"posting_time": posting_time
}
last_entry = get_previous_sle(args)
if with_valuation_rate:
return (last_entry.qty_after_transaction, last_entry.valuation_rate) if last_entry else (0.0, 0.0)
if with_serial_no:
serial_nos = last_entry.get("serial_no")
if (serial_nos and
len(get_serial_nos_data(serial_nos)) < last_entry.qty_after_transaction):
serial_nos = get_serial_nos_data_after_transactions(args)
return ((last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos)
if last_entry else (0.0, 0.0, 0.0))
else:
return (last_entry.qty_after_transaction, last_entry.valuation_rate) if last_entry else (0.0, 0.0)
else:
return last_entry.qty_after_transaction if last_entry else 0.0
def get_serial_nos_data_after_transactions(args):
serial_nos = []
data = frappe.db.sql(""" SELECT serial_no, actual_qty
FROM `tabStock Ledger Entry`
WHERE
item_code = %(item_code)s and warehouse = %(warehouse)s
and timestamp(posting_date, posting_time) < timestamp(%(posting_date)s, %(posting_time)s)
order by posting_date, posting_time asc """, args, as_dict=1)
for d in data:
if d.actual_qty > 0:
serial_nos.extend(get_serial_nos_data(d.serial_no))
else:
serial_nos = list(set(serial_nos) - set(get_serial_nos_data(d.serial_no)))
return '\n'.join(serial_nos)
def get_serial_nos_data(serial_nos):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
return get_serial_nos(serial_nos)
@frappe.whitelist()
def get_latest_stock_qty(item_code, warehouse=None):
values, condition = [item_code], ""