Merge pull request #12638 from adityaduggal/develop
Resolves Issue #12630 -Added Filters to AR
diff --git "a/erpnext/docs/user/manual/en/regional/france/fichier_des_\303\251critures_comptables.md" b/erpnext/docs/user/manual/en/regional/france/fichier_des_ecritures_comptables.md
similarity index 100%
rename from "erpnext/docs/user/manual/en/regional/france/fichier_des_\303\251critures_comptables.md"
rename to erpnext/docs/user/manual/en/regional/france/fichier_des_ecritures_comptables.md
diff --git a/erpnext/docs/user/manual/en/regional/france/index.txt b/erpnext/docs/user/manual/en/regional/france/index.txt
new file mode 100644
index 0000000..2edf323
--- /dev/null
+++ b/erpnext/docs/user/manual/en/regional/france/index.txt
@@ -0,0 +1 @@
+fichier_des_ecritures_comptables
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index ed051b0..b4433fb 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -489,4 +489,5 @@
erpnext.patches.v10_0.fichier_des_ecritures_comptables_for_france
erpnext.patches.v10_0.update_assessment_plan
erpnext.patches.v10_0.update_assessment_result
-erpnext.patches.v10_0.workflow_leave_application
\ No newline at end of file
+erpnext.patches.v10_0.added_extra_gst_custom_field
+erpnext.patches.v10_0.workflow_leave_application #2018-01-24
diff --git a/erpnext/patches/v10_0/added_extra_gst_custom_field.py b/erpnext/patches/v10_0/added_extra_gst_custom_field.py
new file mode 100644
index 0000000..a1512ed
--- /dev/null
+++ b/erpnext/patches/v10_0/added_extra_gst_custom_field.py
@@ -0,0 +1,9 @@
+import frappe
+from erpnext.regional.india.setup import make_custom_fields
+
+def execute():
+ company = frappe.get_all('Company', filters = {'country': 'India'})
+ if not company:
+ return
+
+ make_custom_fields()
\ No newline at end of file
diff --git a/erpnext/patches/v10_0/workflow_leave_application.py b/erpnext/patches/v10_0/workflow_leave_application.py
index 5db5dd9..8a68f89 100644
--- a/erpnext/patches/v10_0/workflow_leave_application.py
+++ b/erpnext/patches/v10_0/workflow_leave_application.py
@@ -3,49 +3,11 @@
from __future__ import unicode_literals
import frappe
+from erpnext.setup.install import leave_application_workflow
def execute():
frappe.reload_doc("hr", "doctype", "leave_application")
frappe.reload_doc("workflow", "doctype", "workflow")
-
- if not frappe.db.exists("Workflow State", "Open"):
- frappe.get_doc({
- 'doctype': 'Workflow State',
- 'workflow_state_name': 'Open',
- 'style': 'Warning'
- }).insert(ignore_permissions=True)
-
- frappe.get_doc({
- 'doctype': 'Workflow',
- 'workflow_name': 'Leave Approval',
- 'document_type': 'Leave Application',
- 'is_active': 1,
- 'workflow_state_field': 'workflow_state',
- 'states': [{
- "state": 'Open',
- "doc_status": 0,
- "allow_edit": 'Employee'
- }, {
- "state": 'Approved',
- "doc_status": 1,
- "allow_edit": 'Leave Approver'
- }, {
- "state": 'Rejected',
- "doc_status": 1,
- "allow_edit": 'Leave Approver'
- }],
- 'transitions': [{
- "state": 'Open',
- "action": 'Approve',
- "next_state": 'Approved',
- "allowed": 'Leave Approver'
- },
- {
- "state": 'Open',
- "action": 'Reject',
- "next_state": 'Rejected',
- "allowed": 'Leave Approver'
- }]
- }).insert(ignore_permissions=True)
-
- frappe.db.sql("""update `tabLeave Application` set workflow_state = status""")
+ leave_application_workflow()
+ if frappe.db.has_column("Leave Application", "status"):
+ frappe.db.sql("""update `tabLeave Application` set workflow_state = status""")
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 7143bd3..e9d91ab 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -103,7 +103,20 @@
depends_on='eval:in_list(["SEZ", "Export", "Deemed Export"], doc.invoice_type)',
options='\nWith Payment of Tax\nWithout Payment of Tax'),
dict(fieldname='ecommerce_gstin', label='E-commerce GSTIN',
- fieldtype='Data', insert_after='export_type', print_hide=1)
+ fieldtype='Data', insert_after='export_type', print_hide=1),
+ dict(fieldname='reason_for_issuing_document', label='Reason For Issuing document',
+ fieldtype='Select', insert_after='ecommerce_gstin', print_hide=1,
+ depends_on='eval:doc.is_return==1',
+ options='\n01-Sales Return\n02-Post Sale Discount\n03-Deficiency in services\n04-Correction in Invoice\n05-Change in POS\n06-Finalization of Provisional assessment\n07-Others'),
+ dict(fieldname='port_code', label='Port Code',
+ fieldtype='Data', insert_after='reason_for_issuing_document', print_hide=1,
+ depends_on="eval:doc.invoice_type=='Export' "),
+ dict(fieldname='shipping_bill_number', label=' Shipping Bill Number',
+ fieldtype='Data', insert_after='port_code', print_hide=1,
+ depends_on="eval:doc.invoice_type=='Export' "),
+ dict(fieldname='shipping_bill_date', label='Shipping Bill Date',
+ fieldtype='Date', insert_after='shipping_bill_number', print_hide=1,
+ depends_on="eval:doc.invoice_type=='Export' "),
]
purchase_invoice_gst_fields = [
diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js
index 9437786..3a63527 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.js
+++ b/erpnext/regional/report/gstr_1/gstr_1.js
@@ -31,7 +31,7 @@
"label": __("Type of Business"),
"fieldtype": "Select",
"reqd": 1,
- "options": ["B2B", "B2C Large", "B2C Small"],
+ "options": ["B2B", "B2C Large", "B2C Small","CDNR", "EXPORT"],
"default": "B2B"
}
]
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 65b1b89..b6df878 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe, json
from frappe import _
+from datetime import date
def execute(filters=None):
return Gstr1Report(filters).run()
@@ -12,7 +13,7 @@
def __init__(self, filters=None):
self.filters = frappe._dict(filters or {})
self.customer_type = "Company" if self.filters.get("type_of_business") == "B2B" else "Individual"
-
+
def run(self):
self.get_columns()
self.get_data()
@@ -35,13 +36,15 @@
for rate, items in items_based_on_rate.items():
row = []
for fieldname in invoice_fields:
- if fieldname == "invoice_value":
+ if self.filters.get("type_of_business") == "CDNR" and fieldname == "invoice_value":
+ row.append(abs(invoice_details.base_rounded_total) or abs(invoice_details.base_grand_total))
+ elif fieldname == "invoice_value":
row.append(invoice_details.base_rounded_total or invoice_details.base_grand_total)
else:
row.append(invoice_details.get(fieldname))
row += [rate,
- sum([net_amount for item_code, net_amount in self.invoice_items.get(inv).items()
+ sum([abs(net_amount) for item_code, net_amount in self.invoice_items.get(inv).items()
if item_code in items]),
self.invoice_cess.get(inv)
]
@@ -49,6 +52,10 @@
if self.filters.get("type_of_business") == "B2C Small":
row.append("E" if invoice_details.ecommerce_gstin else "OE")
+ if self.filters.get("type_of_business") == "CDNR":
+ row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N")
+ row.append("C" if invoice_details.return_against else "R")
+
self.data.append(row)
def get_invoice_data(self):
@@ -66,7 +73,15 @@
place_of_supply,
ecommerce_gstin,
reverse_charge,
- invoice_type
+ invoice_type,
+ return_against,
+ is_return,
+ invoice_type,
+ export_type,
+ port_code,
+ shipping_bill_number,
+ shipping_bill_date,
+ reason_for_issuing_document
from `tabSales Invoice`
where docstatus = 1 %s
order by posting_date desc
@@ -85,18 +100,27 @@
conditions += opts[1]
customers = frappe.get_all("Customer", filters={"customer_type": self.customer_type})
- conditions += " and customer in ('{0}')".format("', '".join([frappe.db.escape(c.name)
- for c in customers]))
+
+ if self.filters.get("type_of_business") == "B2B":
+ conditions += " and invoice_type != 'Export' 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") == "B2C Large":
conditions += """ and SUBSTR(place_of_supply, 1, 2) != SUBSTR(company_gstin, 1, 2)
- and grand_total > 250000"""
+ and grand_total > 250000 and is_return != 1 and customer in ('{0}')""".\
+ format("', '".join([frappe.db.escape(c.name) for c in customers]))
+
elif self.filters.get("type_of_business") == "B2C Small":
conditions += """ and (
SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2)
- or grand_total <= 250000
- )"""
+ or grand_total <= 250000 ) and is_return != 1 and customer in ('{0}')""".\
+ format("', '".join([frappe.db.escape(c.name) for c in customers]))
+ elif self.filters.get("type_of_business") == "CDNR":
+ conditions += """ and is_return = 1 """
+
+ elif self.filters.get("type_of_business") == "EXPORT":
+ conditions += """ and is_return !=1 and invoice_type = 'Export' """
return conditions
def get_invoice_items(self):
@@ -118,7 +142,7 @@
where
parenttype = 'Sales Invoice' and docstatus = 1
and parent in (%s)
- and tax_amount_after_discount_amount > 0
+
order by account_head
""" % (', '.join(['%s']*len(self.invoices.keys()))), tuple(self.invoices.keys()))
@@ -152,7 +176,6 @@
.setdefault(tax_rate, [])
if item_code not in rate_based_dict:
rate_based_dict.append(item_code)
-
except ValueError:
continue
if unidentified_gst_accounts:
@@ -185,12 +208,6 @@
"label": "Taxable Value",
"fieldtype": "Currency",
"width": 100
- },
- {
- "fieldname": "cess_amount",
- "label": "Cess Amount",
- "fieldtype": "Currency",
- "width": 100
}
]
self.other_columns = []
@@ -200,33 +217,39 @@
{
"fieldname": "customer_gstin",
"label": "GSTIN/UIN of Recipient",
- "fieldtype": "Data"
+ "fieldtype": "Data",
+ "width": 150
},
{
"fieldname": "customer_name",
"label": "Receiver Name",
- "fieldtype": "Data"
+ "fieldtype": "Data",
+ "width":100
},
{
"fieldname": "invoice_number",
"label": "Invoice Number",
"fieldtype": "Link",
- "options": "Sales Invoice"
+ "options": "Sales Invoice",
+ "width":100
},
{
"fieldname": "posting_date",
"label": "Invoice date",
- "fieldtype": "Date"
+ "fieldtype": "Date",
+ "width":80
},
{
"fieldname": "invoice_value",
"label": "Invoice Value",
- "fieldtype": "Currency"
+ "fieldtype": "Currency",
+ "width":100
},
{
"fieldname": "place_of_supply",
"label": "Place of Supply",
- "fieldtype": "Data"
+ "fieldtype": "Data",
+ "width":100
},
{
"fieldname": "reverse_charge",
@@ -241,9 +264,19 @@
{
"fieldname": "ecommerce_gstin",
"label": "E-Commerce GSTIN",
- "fieldtype": "Data"
+ "fieldtype": "Data",
+ "width":120
}
]
+ self.other_columns = [
+ {
+ "fieldname": "cess_amount",
+ "label": "Cess Amount",
+ "fieldtype": "Currency",
+ "width": 100
+ }
+ ]
+
elif self.filters.get("type_of_business") == "B2C Large":
self.invoice_columns = [
{
@@ -278,6 +311,93 @@
"width": 130
}
]
+ self.other_columns = [
+ {
+ "fieldname": "cess_amount",
+ "label": "Cess Amount",
+ "fieldtype": "Currency",
+ "width": 100
+ }
+ ]
+ elif self.filters.get("type_of_business") == "CDNR":
+ self.invoice_columns = [
+ {
+ "fieldname": "customer_gstin",
+ "label": "GSTIN/UIN of Recipient",
+ "fieldtype": "Data",
+ "width": 150
+ },
+ {
+ "fieldname": "customer_name",
+ "label": "Receiver Name",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "return_against",
+ "label": "Invoice/Advance Receipt Number",
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width": 120
+ },
+ {
+ "fieldname": "posting_date",
+ "label": "Invoice/Advance Receipt date",
+ "fieldtype": "Date",
+ "width": 120
+ },
+ {
+ "fieldname": "invoice_number",
+ "label": "Invoice/Advance Receipt Number",
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width":120
+ },
+ {
+ "fieldname": "posting_date",
+ "label": "Invoice/Advance Receipt date",
+ "fieldtype": "Date",
+ "width": 120
+ },
+ {
+ "fieldname": "reason_for_issuing_document",
+ "label": "Reason For Issuing document",
+ "fieldtype": "Data",
+ "width": 140
+ },
+ {
+ "fieldname": "place_of_supply",
+ "label": "Place of Supply",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "invoice_value",
+ "label": "Invoice Value",
+ "fieldtype": "Currency",
+ "width": 120
+ }
+ ]
+ self.other_columns = [
+ {
+ "fieldname": "cess_amount",
+ "label": "Cess Amount",
+ "fieldtype": "Currency",
+ "width": 100
+ },
+ {
+ "fieldname": "pre_gst",
+ "label": "PRE GST",
+ "fieldtype": "Data",
+ "width": 80
+ },
+ {
+ "fieldname": "document_type",
+ "label": "Document Type",
+ "fieldtype": "Data",
+ "width": 80
+ }
+ ]
elif self.filters.get("type_of_business") == "B2C Small":
self.invoice_columns = [
{
@@ -295,10 +415,62 @@
]
self.other_columns = [
{
+ "fieldname": "cess_amount",
+ "label": "Cess Amount",
+ "fieldtype": "Currency",
+ "width": 100
+ },
+ {
"fieldname": "type",
"label": "Type",
"fieldtype": "Data",
"width": 50
}
]
- self.columns = self.invoice_columns + self.tax_columns + self.other_columns
\ No newline at end of file
+ elif self.filters.get("type_of_business") == "EXPORT":
+ self.invoice_columns = [
+ {
+ "fieldname": "export_type",
+ "label": "Export Type",
+ "fieldtype": "Data",
+ "width":120
+ },
+ {
+ "fieldname": "invoice_number",
+ "label": "Invoice Number",
+ "fieldtype": "Link",
+ "options": "Sales Invoice",
+ "width":120
+ },
+ {
+ "fieldname": "posting_date",
+ "label": "Invoice date",
+ "fieldtype": "Date",
+ "width": 120
+ },
+ {
+ "fieldname": "invoice_value",
+ "label": "Invoice Value",
+ "fieldtype": "Currency",
+ "width": 120
+ },
+ {
+ "fieldname": "port_code",
+ "label": "Port Code",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "shipping_bill_number",
+ "label": "Shipping Bill Number",
+ "fieldtype": "Data",
+ "width": 120
+ },
+ {
+ "fieldname": "shipping_bill_date",
+ "label": "Shipping Bill Date",
+ "fieldtype": "Date",
+ "width": 120
+ }
+ ]
+ self.columns = self.invoice_columns + self.tax_columns + self.other_columns
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 1c3354c..5bacf28 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -567,10 +567,12 @@
target.base_amount = target.amount * flt(source_parent.conversion_rate)
target.qty = target.amount / flt(source.rate) if (source.rate and source.billed_amt) else source.qty
- item = frappe.db.get_value("Item", target.item_code, ["item_group", "selling_cost_center"], as_dict=1)
- target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center") \
- or item.selling_cost_center \
- or frappe.db.get_value("Item Group", item.item_group, "default_cost_center")
+ if source_parent.project:
+ target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center")
+ if not target.cost_center and target.item_code:
+ item = frappe.db.get_value("Item", target.item_code, ["item_group", "selling_cost_center"], as_dict=1)
+ target.cost_center = item.selling_cost_center \
+ or frappe.db.get_value("Item Group", item.item_group, "default_cost_center")
doclist = get_mapped_doc("Sales Order", source_name, {
"Sales Order": {
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index 81f909a..2cc280f 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -12,6 +12,7 @@
<a style="color: #888" href="http://erpnext.org">ERPNext</a></div>"""
def after_install():
+ leave_application_workflow()
frappe.get_doc({'doctype': "Role", "role_name": "Analytics"}).insert()
set_single_defaults()
create_compact_item_print_custom_field()
@@ -19,6 +20,58 @@
add_all_roles_to("Administrator")
frappe.db.commit()
+def leave_application_workflow():
+ states = {'Approved': 'Success', 'Rejected': 'Danger', 'Open': 'Warning'}
+
+ for state, style in states.items():
+ if not frappe.db.exists("Workflow State", state):
+ frappe.get_doc({
+ 'doctype': 'Workflow State',
+ 'workflow_state_name': state,
+ 'style': style
+ }).insert(ignore_permissions=True)
+
+ for action in ['Approve', 'Reject']:
+ if not frappe.db.exists("Workflow Action", action):
+ frappe.get_doc({
+ 'doctype': 'Workflow Action',
+ 'workflow_action_name': action
+ }).insert(ignore_permissions=True)
+
+ if not frappe.db.exists("Workflow", "Leave Approval"):
+ frappe.get_doc({
+ 'doctype': 'Workflow',
+ 'workflow_name': 'Leave Approval',
+ 'document_type': 'Leave Application',
+ 'is_active': 1,
+ 'workflow_state_field': 'workflow_state',
+ 'states': [{
+ "state": 'Open',
+ "doc_status": 0,
+ "allow_edit": 'Employee'
+ }, {
+ "state": 'Approved',
+ "doc_status": 1,
+ "allow_edit": 'Leave Approver'
+ }, {
+ "state": 'Rejected',
+ "doc_status": 1,
+ "allow_edit": 'Leave Approver'
+ }],
+ 'transitions': [{
+ "state": 'Open',
+ "action": 'Approve',
+ "next_state": 'Approved',
+ "allowed": 'Leave Approver'
+ },
+ {
+ "state": 'Open',
+ "action": 'Reject',
+ "next_state": 'Rejected',
+ "allowed": 'Leave Approver'
+ }]
+ }).insert(ignore_permissions=True)
+
def check_setup_wizard_not_completed():
if frappe.db.get_default('desktop:home_page') == 'desktop':
print()
diff --git a/erpnext/startup/notifications.py b/erpnext/startup/notifications.py
index eb06f27..b8fce6e 100644
--- a/erpnext/startup/notifications.py
+++ b/erpnext/startup/notifications.py
@@ -30,7 +30,7 @@
"docstatus": ("<", 2)
},
"Payment Entry": {"docstatus": 0},
- "Leave Application": {"status": "Open"},
+ "Leave Application": {"docstatus": 0},
"Expense Claim": {"approval_status": "Draft"},
"Job Applicant": {"status": "Open"},
"Delivery Note": {
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index 42e2cef..03ce7b6 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -5,24 +5,31 @@
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.model.naming import make_autoname
+from frappe.model.naming import make_autoname, revert_series_if_last
from frappe.utils import flt, cint
-class UnableToSelectBatchError(frappe.ValidationError): pass
+class UnableToSelectBatchError(frappe.ValidationError):
+ pass
def get_name_from_naming_series():
- naming_series_prefix = frappe.db.get_single_value('Stock Settings', 'naming_series_prefix')
- if not naming_series_prefix:
- naming_series_prefix = 'BATCH-'
-
- name = make_autoname(naming_series_prefix + '.#####')
+ """
+ Get a name generated for a Batch from the Batch's naming series.
+ :return: The string that was generated.
+ """
+ naming_series_prefix = _get_batch_prefix()
+ key = _make_naming_series_key(naming_series_prefix)
+ name = make_autoname(key)
return name
def get_name_from_hash():
+ """
+ Get a name for a Batch by generating a unique hash.
+ :return: The hash that was generated.
+ """
temp = None
while not temp:
temp = frappe.generate_hash()[:7].upper()
@@ -32,13 +39,66 @@
return temp
+def batch_uses_naming_series():
+ """
+ Verify if the Batch is to be named using a naming series
+ :return: bool
+ """
+ use_naming_series = cint(frappe.db.get_single_value('Stock Settings', 'use_naming_series'))
+ return bool(use_naming_series)
+
+
+def _get_batch_prefix():
+ """
+ Get the naming series prefix set in Stock Settings.
+
+ It does not do any sanity checks so make sure to use it after checking if the Batch
+ is set to use naming series.
+ :return: The naming series.
+ """
+ naming_series_prefix = frappe.db.get_single_value('Stock Settings', 'naming_series_prefix')
+ if not naming_series_prefix:
+ naming_series_prefix = 'BATCH-'
+
+ return naming_series_prefix
+
+
+def _make_naming_series_key(prefix):
+ """
+ Make naming series key for a Batch.
+
+ Naming series key is in the format [prefix].[#####]
+ :param prefix: Naming series prefix gotten from Stock Settings
+ :return: The derived key. If no prefix is given, an empty string is returned
+ """
+ if not unicode(prefix):
+ return ''
+ else:
+ return prefix.upper() + '.#####'
+
+
+def get_batch_naming_series():
+ """
+ Get naming series key for a Batch.
+
+ Naming series key is in the format [prefix].[#####]
+ :return: The naming series or empty string if not available
+ """
+ series = ''
+ if batch_uses_naming_series():
+ prefix = _get_batch_prefix()
+ key = _make_naming_series_key(prefix)
+ series = key
+
+ return series
+
+
class Batch(Document):
def autoname(self):
"""Generate random ID for batch if not specified"""
if not self.batch_id:
if frappe.db.get_value('Item', self.item, 'create_new_batch'):
- use_naming_series = frappe.db.get_single_value('Stock Settings', 'use_naming_series')
- if use_naming_series:
+ if batch_uses_naming_series():
self.batch_id = get_name_from_naming_series()
else:
self.batch_id = get_name_from_hash()
@@ -50,13 +110,17 @@
def onload(self):
self.image = frappe.db.get_value('Item', self.item, 'image')
+ def after_delete(self):
+ revert_series_if_last(get_batch_naming_series(), self.name)
+
def validate(self):
self.item_has_batch_enabled()
def item_has_batch_enabled(self):
- if frappe.db.get_value("Item",self.item,"has_batch_no") == 0:
+ if frappe.db.get_value("Item", self.item, "has_batch_no") == 0:
frappe.throw(_("The selected item cannot have Batch"))
+
@frappe.whitelist()
def get_batch_qty(batch_no=None, warehouse=None, item_code=None):
"""Returns batch actual qty if warehouse is passed,
@@ -89,16 +153,18 @@
return out
+
@frappe.whitelist()
def get_batches_by_oldest(item_code, warehouse):
"""Returns the oldest batch and qty for the given item_code and warehouse"""
- batches = get_batch_qty(item_code = item_code, warehouse = warehouse)
+ batches = get_batch_qty(item_code=item_code, warehouse=warehouse)
batches_dates = [[batch, frappe.get_value('Batch', batch.batch_no, 'expiry_date')] for batch in batches]
batches_dates.sort(key=lambda tup: tup[1])
return batches_dates
+
@frappe.whitelist()
-def split_batch(batch_no, item_code, warehouse, qty, new_batch_id = None):
+def split_batch(batch_no, item_code, warehouse, qty, new_batch_id=None):
"""Split the batch into a new batch"""
batch = frappe.get_doc(dict(doctype='Batch', item=item_code, batch_id=new_batch_id)).insert()
stock_entry = frappe.get_doc(dict(
@@ -106,16 +172,16 @@
purpose='Repack',
items=[
dict(
- item_code = item_code,
- qty = float(qty or 0),
- s_warehouse = warehouse,
- batch_no = batch_no
+ item_code=item_code,
+ qty=float(qty or 0),
+ s_warehouse=warehouse,
+ batch_no=batch_no
),
dict(
- item_code = item_code,
- qty = float(qty or 0),
- t_warehouse = warehouse,
- batch_no = batch.name
+ item_code=item_code,
+ qty=float(qty or 0),
+ t_warehouse=warehouse,
+ batch_no=batch.name
),
]
))
@@ -124,7 +190,8 @@
return batch.name
-def set_batch_nos(doc, warehouse_field, throw = False):
+
+def set_batch_nos(doc, warehouse_field, throw=False):
"""Automatically select `batch_no` for outgoing items in item table"""
for d in doc.items:
qty = d.get('stock_qty') or d.get('transfer_qty') or d.get('qty') or 0
@@ -138,6 +205,7 @@
if flt(batch_qty) < flt(qty):
frappe.throw(_("Row #{0}: The batch {1} has only {2} qty. Please select another batch which has {3} qty available or split the row into multiple rows, to deliver/issue from multiple batches").format(d.idx, d.batch_no, batch_qty, d.qty))
+
@frappe.whitelist()
def get_batch_no(item_code, warehouse, qty=1, throw=False):
"""
diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py
index a327b2d..9538781 100644
--- a/erpnext/stock/doctype/batch/test_batch.py
+++ b/erpnext/stock/doctype/batch/test_batch.py
@@ -7,6 +7,8 @@
import unittest
from erpnext.stock.doctype.batch.batch import get_batch_qty, UnableToSelectBatchError, get_batch_no
+from frappe.utils import cint
+
class TestBatch(unittest.TestCase):
@@ -21,7 +23,7 @@
def make_batch_item(cls, item_name):
from erpnext.stock.doctype.item.test_item import make_item
if not frappe.db.exists(item_name):
- make_item(item_name, dict(has_batch_no = 1, create_new_batch = 1))
+ return make_item(item_name, dict(has_batch_no = 1, create_new_batch = 1))
def test_purchase_receipt(self, batch_qty = 100):
'''Test automated batch creation from Purchase Receipt'''
@@ -192,3 +194,37 @@
]
)).insert()
stock_entry.submit()
+
+ def test_batch_name_with_naming_series(self):
+ stock_settings = frappe.get_single('Stock Settings')
+ use_naming_series = cint(stock_settings.use_naming_series)
+
+ if not use_naming_series:
+ frappe.set_value('Stock Settings', 'Stock Settings', 'use_naming_series', 1)
+
+ batch = self.make_new_batch('_Test Stock Item For Batch Test1')
+ batch_name = batch.name
+
+ self.assertTrue(batch_name.startswith('BATCH-'))
+
+ batch.delete()
+ batch = self.make_new_batch('_Test Stock Item For Batch Test2')
+
+ self.assertEqual(batch_name, batch.name)
+
+ # reset Stock Settings
+ if not use_naming_series:
+ frappe.set_value('Stock Settings', 'Stock Settings', 'use_naming_series', 0)
+
+ def make_new_batch(self, item_name, batch_id=None, do_not_insert=0):
+ batch = frappe.new_doc('Batch')
+ item = self.make_batch_item(item_name)
+ batch.item = item.name
+
+ if batch_id:
+ batch.batch_id = batch_id
+
+ if not do_not_insert:
+ batch.insert()
+
+ return batch