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
commit 6ee4c09854
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,
"autoname": "naming_series:",
"creation": "2013-05-24 19:29:05",
@ -373,7 +372,8 @@
"no_copy": 1,
"options": "Sales Invoice",
"print_hide": 1,
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "column_break_21",
@ -1568,8 +1568,7 @@
"icon": "fa fa-file-text",
"idx": 181,
"is_submittable": 1,
"links": [],
"modified": "2019-12-30 19:15:59.580414",
"modified": "2020-02-10 04:57:11.221180",
"modified_by": "Administrator",
"module": "Accounts",
"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):
# 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
if not date:

View File

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

View File

@ -90,7 +90,8 @@ erpnext.SerialNoBatchSelector = Class.extend({
args: {
qty: qty,
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: ""
}
if (this.has_batch) {
if (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):
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
print(rejected_warehouse)
rejected_warehouse="_Test Rejected Warehouse - TCP1"
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)
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.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):
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

View File

@ -29,13 +29,12 @@ class SerialNo(StockController):
self.via_stock_ledger = False
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)
self.set_maintenance_status()
self.validate_warehouse()
self.validate_item()
self.on_stock_ledger_entry()
def set_maintenance_status(self):
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
"""
item = frappe.get_doc("Item", self.item_code)
item = frappe.get_cached_doc("Item", self.item_code)
if item.has_serial_no!=1:
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"):
self.set(fieldname, None)
def get_last_sle(self):
def get_last_sle(self, serial_no=None):
entries = {}
sle_dict = self.get_stock_ledger_entries()
sle_dict = self.get_stock_ledger_entries(serial_no)
if sle_dict:
if sle_dict.get("incoming", []):
entries["purchase_sle"] = sle_dict["incoming"][0]
@ -132,13 +131,28 @@ class SerialNo(StockController):
return entries
def get_stock_ledger_entries(self):
def get_stock_ledger_entries(self, serial_no=None):
sle_dict = {}
for sle in frappe.db.sql("""select * from `tabStock Ledger Entry`
where serial_no like %s and item_code=%s and ifnull(is_cancelled, 'No')='No'
order by posting_date desc, posting_time desc, creation desc""",
("%%%s%%" % self.name, self.item_code), as_dict=1):
if self.name.upper() in get_serial_nos(sle.serial_no):
if not serial_no:
serial_no = self.name
for sle in frappe.db.sql("""
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:
sle_dict.setdefault("incoming", []).append(sle)
else:
@ -178,12 +192,11 @@ class SerialNo(StockController):
where name=%s""" % (dt[0], '%s', '%s'),
('\n'.join(list(serial_nos)), item[0]))
def on_stock_ledger_entry(self):
if self.via_stock_ledger and not self.get("__islocal"):
last_sle = self.get_last_sle()
self.set_purchase_details(last_sle.get("purchase_sle"))
self.set_sales_details(last_sle.get("delivery_sle"))
self.set_maintenance_status()
def update_serial_no_reference(self, serial_no=None):
last_sle = self.get_last_sle(serial_no)
self.set_purchase_details(last_sle.get("purchase_sle"))
self.set_sales_details(last_sle.get("delivery_sle"))
self.set_maintenance_status()
def process_serial_no(sle):
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" \
and not args.get('actual_qty', 0) > 0:
sr.sales_order = None
sr.update_serial_no_reference()
sr.save(ignore_permissions=True)
elif args.get('actual_qty', 0) > 0:
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):
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.item_code = args.get('item_code')
sr.company = args.get('company')
sr.batch_no = args.get('batch_no')
sr.via_stock_ledger = args.get('via_stock_ledger') or True
sr.asset = args.get('asset')
sr.location = args.get('location')
sr.warehouse = args.get('warehouse')
if args.get('purchase_document_type'):
sr.purchase_document_type = args.get('purchase_document_type')
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()
sr.validate_item()
sr.update_serial_no_reference(serial_no)
sr.db_insert()
return sr.name
@ -494,10 +497,11 @@ def get_delivery_note_serial_no(item_code, qty, delivery_note):
return serial_nos
@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={
"item_code": item_code,
"warehouse": warehouse,
"batch_no": batch_no,
"delivery_document_no": "",
"sales_invoice": ""
}, limit=qty, order_by="creation")

View File

@ -1054,7 +1054,7 @@ class StockEntry(StockController):
req_qty_each = flt(req_qty / manufacturing_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:
qty = (req_qty/trans_qty) * flt(self.fg_completed_qty)
else:

View File

@ -180,7 +180,7 @@ def get_child_warehouses(warehouse):
lft, rgt = frappe.get_cached_value("Warehouse", warehouse, [lft, rgt])
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):
warehouses = []
@ -199,4 +199,4 @@ def get_warehouses_based_on_account(account, company=None):
frappe.throw(_("Warehouse not found against the account {0}")
.format(account))
return warehouses
return warehouses

View File

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

909
yarn.lock

File diff suppressed because it is too large Load Diff