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

This commit is contained in:
Khushal Trivedi
2020-02-14 19:23:37 +05:30
17 changed files with 166 additions and 1778 deletions

View File

@@ -1,3 +0,0 @@
{
"baseUrl": "http://test_site_ui:8000"
}

View File

@@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -1,31 +0,0 @@
context('Form', () => {
before(() => {
cy.login('Administrator', 'qwe');
cy.visit('/desk');
});
it('create a new opportunity', () => {
cy.visit('/desk#Form/Opportunity/New Opportunity 1');
cy.get('.page-title').should('contain', 'Not Saved');
cy.fill_field('opportunity_from', 'Customer', 'Select');
cy.fill_field('party_name', 'Test Customer', 'Link').blur();
cy.get('.primary-action').click();
cy.get('.page-title').should('contain', 'Open');
cy.get('.form-inner-toolbar button:contains("Lost")').click({ force: true });
cy.get('.modal input[data-fieldname="lost_reason"]').as('input');
cy.get('@input').focus().type('Higher', { delay: 200 });
cy.get('.modal .awesomplete ul')
.should('be.visible')
.get('li:contains("Higher Price")')
.click({ force: true });
cy.get('@input').focus().type('No Followup', { delay: 200 });
cy.get('.modal .awesomplete ul')
.should('be.visible')
.get('li:contains("No Followup")')
.click();
cy.fill_field('detailed_reason', 'Test Detailed Reason', 'Text');
cy.get('.modal button:contains("Declare Lost")').click({ force: true });
cy.get('.page-title').should('contain', 'Lost');
});
});

View File

@@ -1,17 +0,0 @@
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
// module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
// }

View File

@@ -1,25 +0,0 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

View File

@@ -1,22 +0,0 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// import frappe commands
import '../../../frappe/cypress/support/index';
// Import commands.js using ES2015 syntax:
import './commands';
// Alternatively you can use CommonJS syntax:
// require('./commands')

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-05-24 19:29:05", "creation": "2013-05-24 19:29:05",
@@ -373,7 +372,8 @@
"no_copy": 1, "no_copy": 1,
"options": "Sales Invoice", "options": "Sales Invoice",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"search_index": 1
}, },
{ {
"fieldname": "column_break_21", "fieldname": "column_break_21",
@@ -1568,8 +1568,7 @@
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 181, "idx": 181,
"is_submittable": 1, "is_submittable": 1,
"links": [], "modified": "2020-02-10 04:57:11.221180",
"modified": "2019-12-30 19:15:59.580414",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@@ -10,7 +10,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g
def post_depreciation_entries(date=None): def post_depreciation_entries(date=None):
# Return if automatic booking of asset depreciation is disabled # Return if automatic booking of asset depreciation is disabled
if not cint(frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")): if not cint(frappe.db.get_single_value("Accounts Settings", "book_asset_depreciation_entry_automatically")):
return return
if not date: if not date:

View File

@@ -47,7 +47,7 @@ class ProductionPlan(Document):
'sales_order': data.name, 'sales_order': data.name,
'sales_order_date': data.transaction_date, 'sales_order_date': data.transaction_date,
'customer': data.customer, 'customer': data.customer,
'grand_total': data.grand_total 'grand_total': data.base_grand_total
}) })
def get_pending_material_requests(self): def get_pending_material_requests(self):

View File

@@ -90,7 +90,8 @@ erpnext.SerialNoBatchSelector = Class.extend({
args: { args: {
qty: qty, qty: qty,
item_code: me.item_code, item_code: me.item_code,
warehouse: me.warehouse_details.name warehouse: me.warehouse_details.name,
batch_no: me.item.batch_no || null
} }
}); });
@@ -392,7 +393,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
delivery_document_no: "" delivery_document_no: ""
} }
if (this.has_batch) { if (this.item.batch_no) {
serial_no_filters["batch_no"] = this.item.batch_no; serial_no_filters["batch_no"] = this.item.batch_no;
} }

View File

@@ -158,8 +158,11 @@ class TestPurchaseReceipt(unittest.TestCase):
def test_purchase_return_for_rejected_qty(self): def test_purchase_return_for_rejected_qty(self):
from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse
rejected_warehouse=get_warehouse(company = "_Test Company with perpetual inventory", abbr = " - TCP1", warehouse_name = "_Test Rejected Warehouse").name rejected_warehouse="_Test Rejected Warehouse - TCP1"
print(rejected_warehouse) if not frappe.db.exists("Warehouse", rejected_warehouse):
get_warehouse(company = "_Test Company with perpetual inventory",
abbr = " - TCP1", warehouse_name = "_Test Rejected Warehouse").name
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", received_qty=4, qty=2, rejected_warehouse=rejected_warehouse) pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", received_qty=4, qty=2, rejected_warehouse=rejected_warehouse)
return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, received_qty = -4, qty=-2, rejected_warehouse=rejected_warehouse) return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, received_qty = -4, qty=-2, rejected_warehouse=rejected_warehouse)
@@ -262,6 +265,31 @@ class TestPurchaseReceipt(unittest.TestCase):
self.assertEqual(pr2.per_billed, 80) self.assertEqual(pr2.per_billed, 80)
self.assertEqual(pr2.status, "To Bill") self.assertEqual(pr2.status, "To Bill")
def test_serial_no_against_purchase_receipt(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
item_code = "Test Manual Created Serial No"
if not frappe.db.exists("Item", item_code):
item = make_item(item_code, dict(has_serial_no=1))
serial_no = "12903812901"
pr_doc = make_purchase_receipt(item_code=item_code,
qty=1, serial_no = serial_no)
self.assertEqual(serial_no, frappe.db.get_value("Serial No",
{"purchase_document_type": "Purchase Receipt", "purchase_document_no": pr_doc.name}, "name"))
#check for the auto created serial nos
item_code = "Test Auto Created Serial No"
if not frappe.db.exists("Item", item_code):
item = make_item(item_code, dict(has_serial_no=1, serial_no_series="KLJL.###"))
new_pr_doc = make_purchase_receipt(item_code=item_code, qty=1)
serial_no = get_serial_nos(new_pr_doc.items[0].serial_no)[0]
self.assertEqual(serial_no, frappe.db.get_value("Serial No",
{"purchase_document_type": "Purchase Receipt", "purchase_document_no": new_pr_doc.name}, "name"))
def test_not_accept_duplicate_serial_no(self): def test_not_accept_duplicate_serial_no(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note

View File

@@ -29,13 +29,12 @@ class SerialNo(StockController):
self.via_stock_ledger = False self.via_stock_ledger = False
def validate(self): def validate(self):
if self.get("__islocal") and self.warehouse: if self.get("__islocal") and self.warehouse and not self.via_stock_ledger:
frappe.throw(_("New Serial No cannot have Warehouse. Warehouse must be set by Stock Entry or Purchase Receipt"), SerialNoCannotCreateDirectError) frappe.throw(_("New Serial No cannot have Warehouse. Warehouse must be set by Stock Entry or Purchase Receipt"), SerialNoCannotCreateDirectError)
self.set_maintenance_status() self.set_maintenance_status()
self.validate_warehouse() self.validate_warehouse()
self.validate_item() self.validate_item()
self.on_stock_ledger_entry()
def set_maintenance_status(self): def set_maintenance_status(self):
if not self.warranty_expiry_date and not self.amc_expiry_date: if not self.warranty_expiry_date and not self.amc_expiry_date:
@@ -68,7 +67,7 @@ class SerialNo(StockController):
""" """
Validate whether serial no is required for this item Validate whether serial no is required for this item
""" """
item = frappe.get_doc("Item", self.item_code) item = frappe.get_cached_doc("Item", self.item_code)
if item.has_serial_no!=1: if item.has_serial_no!=1:
frappe.throw(_("Item {0} is not setup for Serial Nos. Check Item master").format(self.item_code)) frappe.throw(_("Item {0} is not setup for Serial Nos. Check Item master").format(self.item_code))
@@ -117,9 +116,9 @@ class SerialNo(StockController):
"warranty_expiry_date"): "warranty_expiry_date"):
self.set(fieldname, None) self.set(fieldname, None)
def get_last_sle(self): def get_last_sle(self, serial_no=None):
entries = {} entries = {}
sle_dict = self.get_stock_ledger_entries() sle_dict = self.get_stock_ledger_entries(serial_no)
if sle_dict: if sle_dict:
if sle_dict.get("incoming", []): if sle_dict.get("incoming", []):
entries["purchase_sle"] = sle_dict["incoming"][0] entries["purchase_sle"] = sle_dict["incoming"][0]
@@ -132,13 +131,28 @@ class SerialNo(StockController):
return entries return entries
def get_stock_ledger_entries(self): def get_stock_ledger_entries(self, serial_no=None):
sle_dict = {} sle_dict = {}
for sle in frappe.db.sql("""select * from `tabStock Ledger Entry` if not serial_no:
where serial_no like %s and item_code=%s and ifnull(is_cancelled, 'No')='No' serial_no = self.name
order by posting_date desc, posting_time desc, creation desc""",
("%%%s%%" % self.name, self.item_code), as_dict=1): for sle in frappe.db.sql("""
if self.name.upper() in get_serial_nos(sle.serial_no): SELECT voucher_type, voucher_no,
posting_date, posting_time, incoming_rate, actual_qty, serial_no
FROM
`tabStock Ledger Entry`
WHERE
item_code=%s AND company = %s AND ifnull(is_cancelled, 'No')='No'
AND (serial_no = %s
OR serial_no like %s
OR serial_no like %s
OR serial_no like %s
)
ORDER BY
posting_date desc, posting_time desc, creation desc""",
(self.item_code, self.company,
serial_no, serial_no+'\n%', '%\n'+serial_no, '%\n'+serial_no+'\n%'), as_dict=1):
if serial_no.upper() in get_serial_nos(sle.serial_no):
if cint(sle.actual_qty) > 0: if cint(sle.actual_qty) > 0:
sle_dict.setdefault("incoming", []).append(sle) sle_dict.setdefault("incoming", []).append(sle)
else: else:
@@ -178,12 +192,11 @@ class SerialNo(StockController):
where name=%s""" % (dt[0], '%s', '%s'), where name=%s""" % (dt[0], '%s', '%s'),
('\n'.join(list(serial_nos)), item[0])) ('\n'.join(list(serial_nos)), item[0]))
def on_stock_ledger_entry(self): def update_serial_no_reference(self, serial_no=None):
if self.via_stock_ledger and not self.get("__islocal"): last_sle = self.get_last_sle(serial_no)
last_sle = self.get_last_sle() self.set_purchase_details(last_sle.get("purchase_sle"))
self.set_purchase_details(last_sle.get("purchase_sle")) self.set_sales_details(last_sle.get("delivery_sle"))
self.set_sales_details(last_sle.get("delivery_sle")) self.set_maintenance_status()
self.set_maintenance_status()
def process_serial_no(sle): def process_serial_no(sle):
item_det = get_item_details(sle.item_code) item_det = get_item_details(sle.item_code)
@@ -368,6 +381,7 @@ def auto_make_serial_nos(args):
if sr.sales_order and voucher_type == "Stock Entry" \ if sr.sales_order and voucher_type == "Stock Entry" \
and not args.get('actual_qty', 0) > 0: and not args.get('actual_qty', 0) > 0:
sr.sales_order = None sr.sales_order = None
sr.update_serial_no_reference()
sr.save(ignore_permissions=True) sr.save(ignore_permissions=True)
elif args.get('actual_qty', 0) > 0: elif args.get('actual_qty', 0) > 0:
created_numbers.append(make_serial_no(serial_no, args)) created_numbers.append(make_serial_no(serial_no, args))
@@ -407,27 +421,16 @@ def get_serial_nos(serial_no):
def make_serial_no(serial_no, args): def make_serial_no(serial_no, args):
sr = frappe.new_doc("Serial No") sr = frappe.new_doc("Serial No")
sr.warehouse = None
sr.dont_update_if_missing.append("warehouse")
sr.flags.ignore_permissions = True
sr.serial_no = serial_no sr.serial_no = serial_no
sr.item_code = args.get('item_code') sr.item_code = args.get('item_code')
sr.company = args.get('company') sr.company = args.get('company')
sr.batch_no = args.get('batch_no') sr.batch_no = args.get('batch_no')
sr.via_stock_ledger = args.get('via_stock_ledger') or True sr.via_stock_ledger = args.get('via_stock_ledger') or True
sr.asset = args.get('asset') sr.warehouse = args.get('warehouse')
sr.location = args.get('location')
sr.validate_item()
if args.get('purchase_document_type'): sr.update_serial_no_reference(serial_no)
sr.purchase_document_type = args.get('purchase_document_type') sr.db_insert()
sr.purchase_document_no = args.get('purchase_document_no')
sr.supplier = args.get('supplier')
sr.insert()
if args.get('warehouse'):
sr.warehouse = args.get('warehouse')
sr.save()
return sr.name return sr.name
@@ -494,10 +497,11 @@ def get_delivery_note_serial_no(item_code, qty, delivery_note):
return serial_nos return serial_nos
@frappe.whitelist() @frappe.whitelist()
def auto_fetch_serial_number(qty, item_code, warehouse): def auto_fetch_serial_number(qty, item_code, warehouse, batch_no=None):
serial_numbers = frappe.get_list("Serial No", filters={ serial_numbers = frappe.get_list("Serial No", filters={
"item_code": item_code, "item_code": item_code,
"warehouse": warehouse, "warehouse": warehouse,
"batch_no": batch_no,
"delivery_document_no": "", "delivery_document_no": "",
"sales_invoice": "" "sales_invoice": ""
}, limit=qty, order_by="creation") }, limit=qty, order_by="creation")

View File

@@ -1054,7 +1054,7 @@ class StockEntry(StockController):
req_qty_each = flt(req_qty / manufacturing_qty) req_qty_each = flt(req_qty / manufacturing_qty)
consumed_qty = flt(req_items[0].consumed_qty) consumed_qty = flt(req_items[0].consumed_qty)
if trans_qty and manufacturing_qty >= (produced_qty + flt(self.fg_completed_qty)): if trans_qty and manufacturing_qty > (produced_qty + flt(self.fg_completed_qty)):
if qty >= req_qty: if qty >= req_qty:
qty = (req_qty/trans_qty) * flt(self.fg_completed_qty) qty = (req_qty/trans_qty) * flt(self.fg_completed_qty)
else: else:

View File

@@ -180,7 +180,7 @@ def get_child_warehouses(warehouse):
lft, rgt = frappe.get_cached_value("Warehouse", warehouse, [lft, rgt]) lft, rgt = frappe.get_cached_value("Warehouse", warehouse, [lft, rgt])
return frappe.db.sql_list("""select name from `tabWarehouse` return frappe.db.sql_list("""select name from `tabWarehouse`
where lft >= %s and rgt =< %s""", (lft, rgt)) where lft >= %s and rgt <= %s""", (lft, rgt))
def get_warehouses_based_on_account(account, company=None): def get_warehouses_based_on_account(account, company=None):
warehouses = [] warehouses = []
@@ -199,4 +199,4 @@ def get_warehouses_based_on_account(account, company=None):
frappe.throw(_("Warehouse not found against the account {0}") frappe.throw(_("Warehouse not found against the account {0}")
.format(account)) .format(account))
return warehouses return warehouses

View File

@@ -1,6 +1,5 @@
{ {
"dependencies": { "devdependencies": {
"cypress": "^3.4.1",
"snyk": "^1.290.1" "snyk": "^1.290.1"
}, },
"scripts": { "scripts": {

909
yarn.lock

File diff suppressed because it is too large Load Diff