mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-24 23:49:19 +00:00
Merge branch 'version-13-hotfix' into crm-lead-validate-cleanup
This commit is contained in:
@@ -78,6 +78,7 @@ frappe.treeview_settings["Account"] = {
|
|||||||
const format = (value, currency) => format_currency(Math.abs(value), currency);
|
const format = (value, currency) => format_currency(Math.abs(value), currency);
|
||||||
|
|
||||||
if (account.balance!==undefined) {
|
if (account.balance!==undefined) {
|
||||||
|
node.parent && node.parent.find('.balance-area').remove();
|
||||||
$('<span class="balance-area pull-right">'
|
$('<span class="balance-area pull-right">'
|
||||||
+ (account.balance_in_account_currency ?
|
+ (account.balance_in_account_currency ?
|
||||||
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
|
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
|
||||||
@@ -175,7 +176,7 @@ frappe.treeview_settings["Account"] = {
|
|||||||
&& node.expandable && !node.hide_add;
|
&& node.expandable && !node.hide_add;
|
||||||
},
|
},
|
||||||
click: function() {
|
click: function() {
|
||||||
var me = frappe.treeview_settings['Account'].treeview;
|
var me = frappe.views.trees['Account'];
|
||||||
me.new_node();
|
me.new_node();
|
||||||
},
|
},
|
||||||
btnClass: "hidden-xs"
|
btnClass: "hidden-xs"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from erpnext.accounts.doctype.account.test_account import create_account, get_in
|
|||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||||
from erpnext.controllers.accounts_controller import get_payment_terms
|
from erpnext.controllers.accounts_controller import get_payment_terms
|
||||||
|
from erpnext.controllers.buying_controller import QtyMismatchError
|
||||||
from erpnext.exceptions import InvalidCurrency
|
from erpnext.exceptions import InvalidCurrency
|
||||||
from erpnext.projects.doctype.project.test_project import make_project
|
from erpnext.projects.doctype.project.test_project import make_project
|
||||||
from erpnext.stock.doctype.item.test_item import create_item
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
@@ -36,6 +37,27 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
def tearDownClass(self):
|
def tearDownClass(self):
|
||||||
unlink_payment_on_cancel_of_invoice(0)
|
unlink_payment_on_cancel_of_invoice(0)
|
||||||
|
|
||||||
|
def test_purchase_invoice_received_qty(self):
|
||||||
|
"""
|
||||||
|
1. Test if received qty is validated against accepted + rejected
|
||||||
|
2. Test if received qty is auto set on save
|
||||||
|
"""
|
||||||
|
pi = make_purchase_invoice(
|
||||||
|
qty=1,
|
||||||
|
rejected_qty=1,
|
||||||
|
received_qty=3,
|
||||||
|
item_code="_Test Item Home Desktop 200",
|
||||||
|
rejected_warehouse = "_Test Rejected Warehouse - _TC",
|
||||||
|
update_stock=True, do_not_save=True)
|
||||||
|
self.assertRaises(QtyMismatchError, pi.save)
|
||||||
|
|
||||||
|
pi.items[0].received_qty = 0
|
||||||
|
pi.save()
|
||||||
|
self.assertEqual(pi.items[0].received_qty, 2)
|
||||||
|
|
||||||
|
# teardown
|
||||||
|
pi.delete()
|
||||||
|
|
||||||
def test_gl_entries_without_perpetual_inventory(self):
|
def test_gl_entries_without_perpetual_inventory(self):
|
||||||
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
|
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
|
||||||
pi = frappe.copy_doc(test_records[0])
|
pi = frappe.copy_doc(test_records[0])
|
||||||
|
|||||||
@@ -22,10 +22,10 @@
|
|||||||
"received_qty",
|
"received_qty",
|
||||||
"qty",
|
"qty",
|
||||||
"rejected_qty",
|
"rejected_qty",
|
||||||
"stock_uom",
|
|
||||||
"col_break2",
|
"col_break2",
|
||||||
"uom",
|
"uom",
|
||||||
"conversion_factor",
|
"conversion_factor",
|
||||||
|
"stock_uom",
|
||||||
"stock_qty",
|
"stock_qty",
|
||||||
"sec_break1",
|
"sec_break1",
|
||||||
"price_list_rate",
|
"price_list_rate",
|
||||||
@@ -175,7 +175,8 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "received_qty",
|
"fieldname": "received_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Received Qty"
|
"label": "Received Qty",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bold": 1,
|
"bold": 1,
|
||||||
@@ -223,7 +224,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "stock_qty",
|
"fieldname": "stock_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Stock Qty",
|
"label": "Accepted Qty in Stock UOM",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
@@ -870,10 +871,11 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-09-01 16:04:03.538643",
|
"modified": "2021-11-15 17:04:07.191013",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Item",
|
"name": "Purchase Invoice Item",
|
||||||
|
"naming_rule": "Random",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ frappe.ui.form.on('Asset Value Adjustment', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
frm.set_query('asset', function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
calculate_depreciation: 1,
|
||||||
|
docstatus: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ from frappe.utils import cint, date_diff, flt, formatdate, getdate
|
|||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
get_checks_for_pl_and_bs_accounts,
|
get_checks_for_pl_and_bs_accounts,
|
||||||
)
|
)
|
||||||
|
from erpnext.assets.doctype.asset.asset import get_depreciation_amount
|
||||||
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
|
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
|
||||||
|
from erpnext.regional.india.utils import (
|
||||||
|
get_depreciation_amount as get_depreciation_amount_for_india,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AssetValueAdjustment(Document):
|
class AssetValueAdjustment(Document):
|
||||||
@@ -92,6 +96,7 @@ class AssetValueAdjustment(Document):
|
|||||||
|
|
||||||
def reschedule_depreciations(self, asset_value):
|
def reschedule_depreciations(self, asset_value):
|
||||||
asset = frappe.get_doc('Asset', self.asset)
|
asset = frappe.get_doc('Asset', self.asset)
|
||||||
|
country = frappe.get_value('Company', self.company, 'country')
|
||||||
|
|
||||||
for d in asset.finance_books:
|
for d in asset.finance_books:
|
||||||
d.value_after_depreciation = asset_value
|
d.value_after_depreciation = asset_value
|
||||||
@@ -113,8 +118,10 @@ class AssetValueAdjustment(Document):
|
|||||||
depreciation_amount = days * rate_per_day
|
depreciation_amount = days * rate_per_day
|
||||||
from_date = data.schedule_date
|
from_date = data.schedule_date
|
||||||
else:
|
else:
|
||||||
depreciation_amount = asset.get_depreciation_amount(value_after_depreciation,
|
if country == "India":
|
||||||
no_of_depreciations, d)
|
depreciation_amount = get_depreciation_amount_for_india(asset, value_after_depreciation, d)
|
||||||
|
else:
|
||||||
|
depreciation_amount = get_depreciation_amount(asset, value_after_depreciation, d)
|
||||||
|
|
||||||
if depreciation_amount:
|
if depreciation_amount:
|
||||||
value_after_depreciation -= flt(depreciation_amount)
|
value_after_depreciation -= flt(depreciation_amount)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint
|
from frappe import ValidationError, _, msgprint
|
||||||
from frappe.contacts.doctype.address.address import get_address_display
|
from frappe.contacts.doctype.address.address import get_address_display
|
||||||
from frappe.utils import cint, cstr, flt, getdate
|
from frappe.utils import cint, cstr, flt, getdate
|
||||||
|
|
||||||
@@ -18,6 +18,9 @@ from erpnext.stock.get_item_details import get_conversion_factor
|
|||||||
from erpnext.stock.utils import get_incoming_rate
|
from erpnext.stock.utils import get_incoming_rate
|
||||||
|
|
||||||
|
|
||||||
|
class QtyMismatchError(ValidationError):
|
||||||
|
pass
|
||||||
|
|
||||||
class BuyingController(StockController, Subcontracting):
|
class BuyingController(StockController, Subcontracting):
|
||||||
|
|
||||||
def get_feed(self):
|
def get_feed(self):
|
||||||
@@ -361,19 +364,15 @@ class BuyingController(StockController, Subcontracting):
|
|||||||
def validate_accepted_rejected_qty(self):
|
def validate_accepted_rejected_qty(self):
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
self.validate_negative_quantity(d, ["received_qty","qty", "rejected_qty"])
|
self.validate_negative_quantity(d, ["received_qty","qty", "rejected_qty"])
|
||||||
if not flt(d.received_qty) and flt(d.qty):
|
|
||||||
d.received_qty = flt(d.qty) - flt(d.rejected_qty)
|
|
||||||
|
|
||||||
elif not flt(d.qty) and flt(d.rejected_qty):
|
if not flt(d.received_qty) and (flt(d.qty) or flt(d.rejected_qty)):
|
||||||
d.qty = flt(d.received_qty) - flt(d.rejected_qty)
|
d.received_qty = flt(d.qty) + flt(d.rejected_qty)
|
||||||
|
|
||||||
elif not flt(d.rejected_qty):
|
|
||||||
d.rejected_qty = flt(d.received_qty) - flt(d.qty)
|
|
||||||
|
|
||||||
val = flt(d.qty) + flt(d.rejected_qty)
|
|
||||||
# Check Received Qty = Accepted Qty + Rejected Qty
|
# Check Received Qty = Accepted Qty + Rejected Qty
|
||||||
|
val = flt(d.qty) + flt(d.rejected_qty)
|
||||||
if (flt(val, d.precision("received_qty")) != flt(d.received_qty, d.precision("received_qty"))):
|
if (flt(val, d.precision("received_qty")) != flt(d.received_qty, d.precision("received_qty"))):
|
||||||
frappe.throw(_("Accepted + Rejected Qty must be equal to Received quantity for Item {0}").format(d.item_code))
|
message = _("Row #{0}: Received Qty must be equal to Accepted + Rejected Qty for Item {1}").format(d.idx, d.item_code)
|
||||||
|
frappe.throw(msg=message, title=_("Mismatch"), exc=QtyMismatchError)
|
||||||
|
|
||||||
def validate_negative_quantity(self, item_row, field_list):
|
def validate_negative_quantity(self, item_row, field_list):
|
||||||
if self.is_return:
|
if self.is_return:
|
||||||
|
|||||||
@@ -680,11 +680,6 @@ frappe.ui.form.on("BOM Item", "items_remove", function(frm) {
|
|||||||
erpnext.bom.calculate_total(frm.doc);
|
erpnext.bom.calculate_total(frm.doc);
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on("BOM", "with_operations", function(frm) {
|
|
||||||
if(!cint(frm.doc.with_operations)) {
|
|
||||||
frm.set_value("operations", []);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
frappe.ui.form.on("BOM Scrap Item", {
|
frappe.ui.form.on("BOM Scrap Item", {
|
||||||
item_code(frm, cdt, cdn) {
|
item_code(frm, cdt, cdn) {
|
||||||
|
|||||||
@@ -237,6 +237,7 @@
|
|||||||
"options": "Price List"
|
"options": "Price List"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "with_operations",
|
||||||
"fieldname": "operations_section",
|
"fieldname": "operations_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hide_border": 1,
|
"hide_border": 1,
|
||||||
@@ -539,7 +540,7 @@
|
|||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-10-27 14:52:04.500251",
|
"modified": "2021-11-18 13:04:16.271975",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM",
|
"name": "BOM",
|
||||||
|
|||||||
@@ -23,12 +23,6 @@ frappe.ui.form.on('Job Card', {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
|
||||||
if (frm.doc.scrap_items.length == 0) {
|
|
||||||
frm.fields_dict['scrap_items_section'].collapse();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
frappe.flags.pause_job = 0;
|
frappe.flags.pause_job = 0;
|
||||||
frappe.flags.resume_job = 0;
|
frappe.flags.resume_job = 0;
|
||||||
|
|||||||
@@ -164,44 +164,32 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
|
|||||||
this.price_list_rate(doc, cdt, cdn);
|
this.price_list_rate(doc, cdt, cdn);
|
||||||
},
|
},
|
||||||
|
|
||||||
qty: function(doc, cdt, cdn) {
|
qty(doc, cdt, cdn) {
|
||||||
var item = frappe.get_doc(cdt, cdn);
|
|
||||||
if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && (doc.update_stock || doc.is_return))) {
|
if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && (doc.update_stock || doc.is_return))) {
|
||||||
frappe.model.round_floats_in(item, ["qty", "received_qty"]);
|
this.calculate_received_qty(doc, cdt, cdn)
|
||||||
|
|
||||||
if(!doc.is_return && this.validate_negative_quantity(cdt, cdn, item, ["qty", "received_qty"])){ return }
|
|
||||||
|
|
||||||
if(!item.rejected_qty && item.qty) {
|
|
||||||
item.received_qty = item.qty;
|
|
||||||
}
|
|
||||||
|
|
||||||
frappe.model.round_floats_in(item, ["qty", "received_qty"]);
|
|
||||||
item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item));
|
|
||||||
item.received_stock_qty = flt(item.conversion_factor, precision("conversion_factor", item)) * flt(item.received_qty);
|
|
||||||
}
|
}
|
||||||
this._super(doc, cdt, cdn);
|
this._super(doc, cdt, cdn);
|
||||||
},
|
},
|
||||||
|
|
||||||
batch_no: function(doc, cdt, cdn) {
|
rejected_qty(doc, cdt, cdn) {
|
||||||
this._super(doc, cdt, cdn);
|
this.calculate_received_qty(doc, cdt, cdn)
|
||||||
},
|
},
|
||||||
|
|
||||||
received_qty: function(doc, cdt, cdn) {
|
calculate_received_qty(doc, cdt, cdn){
|
||||||
this.calculate_accepted_qty(doc, cdt, cdn)
|
|
||||||
},
|
|
||||||
|
|
||||||
rejected_qty: function(doc, cdt, cdn) {
|
|
||||||
this.calculate_accepted_qty(doc, cdt, cdn)
|
|
||||||
},
|
|
||||||
|
|
||||||
calculate_accepted_qty: function(doc, cdt, cdn){
|
|
||||||
var item = frappe.get_doc(cdt, cdn);
|
var item = frappe.get_doc(cdt, cdn);
|
||||||
frappe.model.round_floats_in(item, ["received_qty", "rejected_qty"]);
|
frappe.model.round_floats_in(item, ["qty", "rejected_qty"]);
|
||||||
|
|
||||||
if(!doc.is_return && this.validate_negative_quantity(cdt, cdn, item, ["received_qty", "rejected_qty"])){ return }
|
if(!doc.is_return && this.validate_negative_quantity(cdt, cdn, item, ["qty", "rejected_qty"])){ return }
|
||||||
|
|
||||||
item.qty = flt(item.received_qty - item.rejected_qty, precision("qty", item));
|
let received_qty = flt(item.qty + item.rejected_qty, precision("received_qty", item));
|
||||||
this.qty(doc, cdt, cdn);
|
let received_stock_qty = flt(item.conversion_factor, precision("conversion_factor", item)) * flt(received_qty);
|
||||||
|
|
||||||
|
frappe.model.set_value(cdt, cdn, "received_qty", received_qty);
|
||||||
|
frappe.model.set_value(cdt, cdn, "received_stock_qty", received_stock_qty);
|
||||||
|
},
|
||||||
|
|
||||||
|
batch_no(doc, cdt, cdn) {
|
||||||
|
super.batch_no(doc, cdt, cdn);
|
||||||
},
|
},
|
||||||
|
|
||||||
validate_negative_quantity: function(cdt, cdn, item, fieldnames){
|
validate_negative_quantity: function(cdt, cdn, item, fieldnames){
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
"sandbox_mode",
|
"sandbox_mode",
|
||||||
"applicable_from",
|
"applicable_from",
|
||||||
"credentials",
|
"credentials",
|
||||||
|
"advanced_settings_section",
|
||||||
|
"client_id",
|
||||||
|
"column_break_8",
|
||||||
|
"client_secret",
|
||||||
"auth_token",
|
"auth_token",
|
||||||
"token_expiry"
|
"token_expiry"
|
||||||
],
|
],
|
||||||
@@ -56,12 +60,32 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Applicable From",
|
"label": "Applicable From",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "advanced_settings_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Advanced Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "client_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Client ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "client_secret",
|
||||||
|
"fieldtype": "Password",
|
||||||
|
"label": "Client Secret"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_8",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-30 12:26:25.538294",
|
"modified": "2021-11-16 19:50:28.029517",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Regional",
|
"module": "Regional",
|
||||||
"name": "E Invoice Settings",
|
"name": "E Invoice Settings",
|
||||||
|
|||||||
@@ -105,6 +105,45 @@ class TestGSTR3BReport(unittest.TestCase):
|
|||||||
gst_settings.round_off_gst_values = 1
|
gst_settings.round_off_gst_values = 1
|
||||||
gst_settings.save()
|
gst_settings.save()
|
||||||
|
|
||||||
|
def test_gst_category_auto_update(self):
|
||||||
|
if not frappe.db.exists("Customer", "_Test GST Customer With GSTIN"):
|
||||||
|
customer = frappe.get_doc({
|
||||||
|
"customer_group": "_Test Customer Group",
|
||||||
|
"customer_name": "_Test GST Customer With GSTIN",
|
||||||
|
"customer_type": "Individual",
|
||||||
|
"doctype": "Customer",
|
||||||
|
"territory": "_Test Territory"
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
self.assertEqual(customer.gst_category, 'Unregistered')
|
||||||
|
|
||||||
|
if not frappe.db.exists('Address', '_Test GST Category-1-Billing'):
|
||||||
|
address = frappe.get_doc({
|
||||||
|
"address_line1": "_Test Address Line 1",
|
||||||
|
"address_title": "_Test GST Category-1",
|
||||||
|
"address_type": "Billing",
|
||||||
|
"city": "_Test City",
|
||||||
|
"state": "Test State",
|
||||||
|
"country": "India",
|
||||||
|
"doctype": "Address",
|
||||||
|
"is_primary_address": 1,
|
||||||
|
"phone": "+91 0000000000",
|
||||||
|
"gstin": "29AZWPS7135H1ZG",
|
||||||
|
"gst_state": "Karnataka",
|
||||||
|
"gst_state_number": "29"
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
address.append("links", {
|
||||||
|
"link_doctype": "Customer",
|
||||||
|
"link_name": "_Test GST Customer With GSTIN"
|
||||||
|
})
|
||||||
|
|
||||||
|
address.save()
|
||||||
|
|
||||||
|
customer.load_from_db()
|
||||||
|
self.assertEqual(customer.gst_category, 'Registered Regular')
|
||||||
|
|
||||||
|
|
||||||
def make_sales_invoice():
|
def make_sales_invoice():
|
||||||
si = create_sales_invoice(company="_Test Company GST",
|
si = create_sales_invoice(company="_Test Company GST",
|
||||||
customer = '_Test GST Customer',
|
customer = '_Test GST Customer',
|
||||||
|
|||||||
@@ -151,6 +151,10 @@ def validate_address_fields(address, skip_gstin_validation):
|
|||||||
title=_('Missing Address Fields')
|
title=_('Missing Address Fields')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if address.address_line2 and len(address.address_line2) < 2:
|
||||||
|
# to prevent "The field Address 2 must be a string with a minimum length of 3 and a maximum length of 100"
|
||||||
|
address.address_line2 = ""
|
||||||
|
|
||||||
def get_party_details(address_name, skip_gstin_validation=False):
|
def get_party_details(address_name, skip_gstin_validation=False):
|
||||||
addr = frappe.get_doc('Address', address_name)
|
addr = frappe.get_doc('Address', address_name)
|
||||||
|
|
||||||
@@ -402,6 +406,12 @@ def validate_totals(einvoice):
|
|||||||
if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1:
|
if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1:
|
||||||
frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.'))
|
frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.'))
|
||||||
|
|
||||||
|
if abs(flt(value_details['CgstVal']) + flt(value_details['SgstVal']) - total_item_cgst_value - total_item_sgst_value) > 1:
|
||||||
|
frappe.throw(_('CGST + SGST value of the items is not equal to total CGST + SGST value. Please review taxes for any correction.'))
|
||||||
|
|
||||||
|
if abs(flt(value_details['IgstVal']) - total_item_igst_value) > 1:
|
||||||
|
frappe.throw(_('IGST value of all items is not equal to total IGST value. Please review taxes for any correction.'))
|
||||||
|
|
||||||
if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - flt(value_details['OthChrg']) - total_item_value) > 1:
|
if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - flt(value_details['OthChrg']) - total_item_value) > 1:
|
||||||
frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.'))
|
frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.'))
|
||||||
|
|
||||||
@@ -475,7 +485,11 @@ def make_einvoice(invoice):
|
|||||||
except Exception:
|
except Exception:
|
||||||
show_link_to_error_log(invoice, einvoice)
|
show_link_to_error_log(invoice, einvoice)
|
||||||
|
|
||||||
validate_totals(einvoice)
|
try:
|
||||||
|
validate_totals(einvoice)
|
||||||
|
except Exception:
|
||||||
|
log_error(einvoice)
|
||||||
|
raise
|
||||||
|
|
||||||
return einvoice
|
return einvoice
|
||||||
|
|
||||||
@@ -629,9 +643,11 @@ class GSPConnector():
|
|||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
||||||
def fetch_auth_token(self):
|
def fetch_auth_token(self):
|
||||||
|
client_id = self.e_invoice_settings.client_id or frappe.conf.einvoice_client_id
|
||||||
|
client_secret = self.e_invoice_settings.get_password('client_secret') or frappe.conf.einvoice_client_secret
|
||||||
headers = {
|
headers = {
|
||||||
'gspappid': frappe.conf.einvoice_client_id,
|
'gspappid': client_id,
|
||||||
'gspappsecret': frappe.conf.einvoice_client_secret
|
'gspappsecret': client_secret
|
||||||
}
|
}
|
||||||
res = {}
|
res = {}
|
||||||
try:
|
try:
|
||||||
@@ -936,7 +952,7 @@ class GSPConnector():
|
|||||||
if errors:
|
if errors:
|
||||||
frappe.throw(errors, title=title, as_list=1)
|
frappe.throw(errors, title=title, as_list=1)
|
||||||
else:
|
else:
|
||||||
link_to_error_list = '<a href="/app/error-log">Error Log</a>'
|
link_to_error_list = '<a href="/app/error-log" target="_blank">Error Log</a>'
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_('An error occurred while making e-invoicing request. Please check {} for more information.').format(link_to_error_list),
|
_('An error occurred while making e-invoicing request. Please check {} for more information.').format(link_to_error_list),
|
||||||
title=title,
|
title=title,
|
||||||
|
|||||||
@@ -77,11 +77,11 @@ def validate_tax_category(doc, method):
|
|||||||
frappe.throw(_("Intra State tax category for GST State {0} already exists").format(doc.gst_state))
|
frappe.throw(_("Intra State tax category for GST State {0} already exists").format(doc.gst_state))
|
||||||
|
|
||||||
def update_gst_category(doc, method):
|
def update_gst_category(doc, method):
|
||||||
if hasattr(doc, 'gst_category'):
|
for link in doc.links:
|
||||||
for link in doc.links:
|
if link.link_doctype in ['Customer', 'Supplier']:
|
||||||
if link.link_doctype in ['Customer', 'Supplier']:
|
meta = frappe.get_meta(link.link_doctype)
|
||||||
if doc.get('gstin'):
|
if doc.get('gstin') and meta.has_field('gst_category'):
|
||||||
frappe.db.set_value(link.link_doctype, {'name': link.link_name, 'gst_category': 'Unregistered'}, 'gst_category', 'Registered Regular')
|
frappe.db.set_value(link.link_doctype, {'name': link.link_name, 'gst_category': 'Unregistered'}, 'gst_category', 'Registered Regular')
|
||||||
|
|
||||||
def set_gst_state_and_state_number(doc):
|
def set_gst_state_and_state_number(doc):
|
||||||
if not doc.gst_state:
|
if not doc.gst_state:
|
||||||
|
|||||||
@@ -28,14 +28,22 @@ def create_qr_code(doc, method):
|
|||||||
|
|
||||||
for field in meta.get_image_fields():
|
for field in meta.get_image_fields():
|
||||||
if field.fieldname == 'qr_code':
|
if field.fieldname == 'qr_code':
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
# Creating public url to print format
|
# Creating public url to print format
|
||||||
default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value")
|
default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value")
|
||||||
|
|
||||||
# System Language
|
# System Language
|
||||||
language = frappe.get_system_settings('language')
|
language = frappe.get_system_settings('language')
|
||||||
|
|
||||||
|
params = urlencode({
|
||||||
|
'format': default_print_format or 'Standard',
|
||||||
|
'_lang': language,
|
||||||
|
'key': doc.get_signature()
|
||||||
|
})
|
||||||
|
|
||||||
# creating qr code for the url
|
# creating qr code for the url
|
||||||
url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }"
|
url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?{ params }"
|
||||||
qr_image = io.BytesIO()
|
qr_image = io.BytesIO()
|
||||||
url = qr_create(url, error='L')
|
url = qr_create(url, error='L')
|
||||||
url.png(qr_image, scale=2, quiet_zone=1)
|
url.png(qr_image, scale=2, quiet_zone=1)
|
||||||
|
|||||||
@@ -125,7 +125,8 @@
|
|||||||
"label": "Abbr",
|
"label": "Abbr",
|
||||||
"oldfieldname": "abbr",
|
"oldfieldname": "abbr",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"reqd": 1
|
"reqd": 1,
|
||||||
|
"set_only_once": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:!doc.__islocal && in_list(frappe.user_roles, \"System Manager\")",
|
"depends_on": "eval:!doc.__islocal && in_list(frappe.user_roles, \"System Manager\")",
|
||||||
|
|||||||
@@ -399,44 +399,6 @@ class Company(NestedSet):
|
|||||||
if not frappe.db.get_value('GL Entry', {'company': self.name}):
|
if not frappe.db.get_value('GL Entry', {'company': self.name}):
|
||||||
frappe.db.sql("delete from `tabProcess Deferred Accounting` where company=%s", self.name)
|
frappe.db.sql("delete from `tabProcess Deferred Accounting` where company=%s", self.name)
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def enqueue_replace_abbr(company, old, new):
|
|
||||||
kwargs = dict(queue="long", company=company, old=old, new=new)
|
|
||||||
frappe.enqueue('erpnext.setup.doctype.company.company.replace_abbr', **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def replace_abbr(company, old, new):
|
|
||||||
new = new.strip()
|
|
||||||
if not new:
|
|
||||||
frappe.throw(_("Abbr can not be blank or space"))
|
|
||||||
|
|
||||||
frappe.only_for("System Manager")
|
|
||||||
|
|
||||||
def _rename_record(doc):
|
|
||||||
parts = doc[0].rsplit(" - ", 1)
|
|
||||||
if len(parts) == 1 or parts[1].lower() == old.lower():
|
|
||||||
frappe.rename_doc(dt, doc[0], parts[0] + " - " + new, force=True)
|
|
||||||
|
|
||||||
def _rename_records(dt):
|
|
||||||
# rename is expensive so let's be economical with memory usage
|
|
||||||
doc = (d for d in frappe.db.sql("select name from `tab%s` where company=%s" % (dt, '%s'), company))
|
|
||||||
for d in doc:
|
|
||||||
_rename_record(d)
|
|
||||||
try:
|
|
||||||
frappe.db.auto_commit_on_many_writes = 1
|
|
||||||
frappe.db.set_value("Company", company, "abbr", new)
|
|
||||||
for dt in ["Warehouse", "Account", "Cost Center", "Department",
|
|
||||||
"Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]:
|
|
||||||
_rename_records(dt)
|
|
||||||
frappe.db.commit()
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
frappe.log_error(title=_('Abbreviation Rename Error'))
|
|
||||||
finally:
|
|
||||||
frappe.db.auto_commit_on_many_writes = 0
|
|
||||||
|
|
||||||
|
|
||||||
def get_name_with_abbr(name, company):
|
def get_name_with_abbr(name, company):
|
||||||
company_abbr = frappe.get_cached_value('Company', company, "abbr")
|
company_abbr = frappe.get_cached_value('Company', company, "abbr")
|
||||||
parts = name.split(" - ")
|
parts = name.split(" - ")
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from six import iteritems
|
|||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||||
|
from erpnext.controllers.buying_controller import QtyMismatchError
|
||||||
from erpnext.stock.doctype.item.test_item import create_item, make_item
|
from erpnext.stock.doctype.item.test_item import create_item, make_item
|
||||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
|
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos
|
||||||
@@ -24,20 +25,54 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)
|
frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)
|
||||||
|
|
||||||
|
def test_purchase_receipt_received_qty(self):
|
||||||
|
"""
|
||||||
|
1. Test if received qty is validated against accepted + rejected
|
||||||
|
2. Test if received qty is auto set on save
|
||||||
|
"""
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
qty=1,
|
||||||
|
rejected_qty=1,
|
||||||
|
received_qty=3,
|
||||||
|
item_code="_Test Item Home Desktop 200",
|
||||||
|
do_not_save=True
|
||||||
|
)
|
||||||
|
self.assertRaises(QtyMismatchError, pr.save)
|
||||||
|
|
||||||
|
pr.items[0].received_qty = 0
|
||||||
|
pr.save()
|
||||||
|
self.assertEqual(pr.items[0].received_qty, 2)
|
||||||
|
|
||||||
|
# teardown
|
||||||
|
pr.delete()
|
||||||
|
|
||||||
def test_reverse_purchase_receipt_sle(self):
|
def test_reverse_purchase_receipt_sle(self):
|
||||||
|
|
||||||
pr = make_purchase_receipt(qty=0.5, item_code="_Test Item Home Desktop 200")
|
pr = make_purchase_receipt(qty=0.5, item_code="_Test Item Home Desktop 200")
|
||||||
|
|
||||||
sl_entry = frappe.db.get_all("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
|
sl_entry = frappe.db.get_all(
|
||||||
"voucher_no": pr.name}, ['actual_qty'])
|
"Stock Ledger Entry",
|
||||||
|
{
|
||||||
|
"voucher_type": "Purchase Receipt",
|
||||||
|
"voucher_no": pr.name
|
||||||
|
},
|
||||||
|
['actual_qty']
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(len(sl_entry), 1)
|
self.assertEqual(len(sl_entry), 1)
|
||||||
self.assertEqual(sl_entry[0].actual_qty, 0.5)
|
self.assertEqual(sl_entry[0].actual_qty, 0.5)
|
||||||
|
|
||||||
pr.cancel()
|
pr.cancel()
|
||||||
|
|
||||||
sl_entry_cancelled = frappe.db.get_all("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
|
sl_entry_cancelled = frappe.db.get_all(
|
||||||
"voucher_no": pr.name}, ['actual_qty'], order_by='creation')
|
"Stock Ledger Entry",
|
||||||
|
{
|
||||||
|
"voucher_type": "Purchase Receipt",
|
||||||
|
"voucher_no": pr.name
|
||||||
|
},
|
||||||
|
['actual_qty'],
|
||||||
|
order_by='creation'
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(len(sl_entry_cancelled), 2)
|
self.assertEqual(len(sl_entry_cancelled), 2)
|
||||||
self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5)
|
self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5)
|
||||||
@@ -63,8 +98,15 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
}]
|
}]
|
||||||
}).insert()
|
}).insert()
|
||||||
|
|
||||||
template = frappe.db.get_value('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice')
|
template = frappe.db.get_value(
|
||||||
old_template_in_supplier = frappe.db.get_value("Supplier", "_Test Supplier", "payment_terms")
|
"Payment Terms Template",
|
||||||
|
"_Test Payment Terms Template For Purchase Invoice"
|
||||||
|
)
|
||||||
|
old_template_in_supplier = frappe.db.get_value(
|
||||||
|
"Supplier",
|
||||||
|
"_Test Supplier",
|
||||||
|
"payment_terms"
|
||||||
|
)
|
||||||
frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", template)
|
frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", template)
|
||||||
|
|
||||||
pr = make_purchase_receipt(do_not_save=True)
|
pr = make_purchase_receipt(do_not_save=True)
|
||||||
@@ -90,30 +132,59 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
# teardown
|
# teardown
|
||||||
pi.delete() # draft PI
|
pi.delete() # draft PI
|
||||||
pr.cancel()
|
pr.cancel()
|
||||||
frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", old_template_in_supplier)
|
frappe.db.set_value(
|
||||||
frappe.get_doc('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice').delete()
|
"Supplier",
|
||||||
|
"_Test Supplier",
|
||||||
|
"payment_terms",
|
||||||
|
old_template_in_supplier
|
||||||
|
)
|
||||||
|
frappe.get_doc(
|
||||||
|
"Payment Terms Template",
|
||||||
|
"_Test Payment Terms Template For Purchase Invoice"
|
||||||
|
).delete()
|
||||||
|
|
||||||
def test_purchase_receipt_no_gl_entry(self):
|
def test_purchase_receipt_no_gl_entry(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
|
||||||
|
|
||||||
company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company')
|
existing_bin_qty, existing_bin_stock_value = frappe.db.get_value(
|
||||||
|
"Bin",
|
||||||
existing_bin_qty, existing_bin_stock_value = frappe.db.get_value("Bin", {"item_code": "_Test Item",
|
{
|
||||||
"warehouse": "_Test Warehouse - _TC"}, ["actual_qty", "stock_value"])
|
"item_code": "_Test Item",
|
||||||
|
"warehouse": "_Test Warehouse - _TC"
|
||||||
|
},
|
||||||
|
["actual_qty", "stock_value"]
|
||||||
|
)
|
||||||
|
|
||||||
if existing_bin_qty < 0:
|
if existing_bin_qty < 0:
|
||||||
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=abs(existing_bin_qty))
|
make_stock_entry(
|
||||||
|
item_code="_Test Item",
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
qty=abs(existing_bin_qty)
|
||||||
|
)
|
||||||
|
|
||||||
pr = make_purchase_receipt()
|
pr = make_purchase_receipt()
|
||||||
|
|
||||||
stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
|
stock_value_difference = frappe.db.get_value(
|
||||||
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name,
|
"Stock Ledger Entry",
|
||||||
"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}, "stock_value_difference")
|
{
|
||||||
|
"voucher_type": "Purchase Receipt",
|
||||||
|
"voucher_no": pr.name,
|
||||||
|
"item_code": "_Test Item",
|
||||||
|
"warehouse": "_Test Warehouse - _TC"
|
||||||
|
},
|
||||||
|
"stock_value_difference"
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(stock_value_difference, 250)
|
self.assertEqual(stock_value_difference, 250)
|
||||||
|
|
||||||
current_bin_stock_value = frappe.db.get_value("Bin", {"item_code": "_Test Item",
|
current_bin_stock_value = frappe.db.get_value(
|
||||||
"warehouse": "_Test Warehouse - _TC"}, "stock_value")
|
"Bin",
|
||||||
|
{
|
||||||
|
"item_code": "_Test Item",
|
||||||
|
"warehouse": "_Test Warehouse - _TC"
|
||||||
|
},
|
||||||
|
"stock_value"
|
||||||
|
)
|
||||||
self.assertEqual(current_bin_stock_value, existing_bin_stock_value + 250)
|
self.assertEqual(current_bin_stock_value, existing_bin_stock_value + 250)
|
||||||
|
|
||||||
self.assertFalse(get_gl_entries("Purchase Receipt", pr.name))
|
self.assertFalse(get_gl_entries("Purchase Receipt", pr.name))
|
||||||
@@ -135,13 +206,17 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
|
|
||||||
pr = make_purchase_receipt(item_code=item.name, qty=5, rate=500)
|
pr = make_purchase_receipt(item_code=item.name, qty=5, rate=500)
|
||||||
|
|
||||||
self.assertTrue(frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name}))
|
self.assertTrue(
|
||||||
|
frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name})
|
||||||
|
)
|
||||||
|
|
||||||
pr.load_from_db()
|
pr.load_from_db()
|
||||||
batch_no = pr.items[0].batch_no
|
batch_no = pr.items[0].batch_no
|
||||||
pr.cancel()
|
pr.cancel()
|
||||||
|
|
||||||
self.assertFalse(frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name}))
|
self.assertFalse(
|
||||||
|
frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name})
|
||||||
|
)
|
||||||
self.assertFalse(frappe.db.get_all('Serial No', {'batch_no': batch_no}))
|
self.assertFalse(frappe.db.get_all('Serial No', {'batch_no': batch_no}))
|
||||||
|
|
||||||
def test_duplicate_serial_nos(self):
|
def test_duplicate_serial_nos(self):
|
||||||
@@ -160,42 +235,78 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
pr = make_purchase_receipt(item_code=item.name, qty=2, rate=500)
|
pr = make_purchase_receipt(item_code=item.name, qty=2, rate=500)
|
||||||
pr.load_from_db()
|
pr.load_from_db()
|
||||||
|
|
||||||
serial_nos = frappe.db.get_value('Stock Ledger Entry',
|
serial_nos = frappe.db.get_value(
|
||||||
{'voucher_type': 'Purchase Receipt', 'voucher_no': pr.name, 'item_code': item.name}, 'serial_no')
|
"Stock Ledger Entry",
|
||||||
|
{
|
||||||
|
"voucher_type": "Purchase Receipt",
|
||||||
|
"voucher_no": pr.name,
|
||||||
|
"item_code": item.name
|
||||||
|
},
|
||||||
|
"serial_no"
|
||||||
|
)
|
||||||
|
|
||||||
serial_nos = get_serial_nos(serial_nos)
|
serial_nos = get_serial_nos(serial_nos)
|
||||||
|
|
||||||
self.assertEquals(get_serial_nos(pr.items[0].serial_no), serial_nos)
|
self.assertEquals(get_serial_nos(pr.items[0].serial_no), serial_nos)
|
||||||
|
|
||||||
# Then tried to receive same serial nos in difference company
|
# Then tried to receive same serial nos in difference company
|
||||||
pr_different_company = make_purchase_receipt(item_code=item.name, qty=2, rate=500,
|
pr_different_company = make_purchase_receipt(
|
||||||
serial_no='\n'.join(serial_nos), company='_Test Company 1', do_not_submit=True,
|
item_code=item.name,
|
||||||
warehouse = 'Stores - _TC1')
|
qty=2,
|
||||||
|
rate=500,
|
||||||
|
serial_no='\n'.join(serial_nos),
|
||||||
|
company='_Test Company 1',
|
||||||
|
do_not_submit=True,
|
||||||
|
warehouse = 'Stores - _TC1'
|
||||||
|
)
|
||||||
|
|
||||||
self.assertRaises(SerialNoDuplicateError, pr_different_company.submit)
|
self.assertRaises(SerialNoDuplicateError, pr_different_company.submit)
|
||||||
|
|
||||||
# Then made delivery note to remove the serial nos from stock
|
# Then made delivery note to remove the serial nos from stock
|
||||||
dn = create_delivery_note(item_code=item.name, qty=2, rate = 1500, serial_no='\n'.join(serial_nos))
|
dn = create_delivery_note(
|
||||||
|
item_code=item.name,
|
||||||
|
qty=2,
|
||||||
|
rate=1500,
|
||||||
|
serial_no='\n'.join(serial_nos)
|
||||||
|
)
|
||||||
dn.load_from_db()
|
dn.load_from_db()
|
||||||
self.assertEquals(get_serial_nos(dn.items[0].serial_no), serial_nos)
|
self.assertEquals(get_serial_nos(dn.items[0].serial_no), serial_nos)
|
||||||
|
|
||||||
posting_date = add_days(today(), -3)
|
posting_date = add_days(today(), -3)
|
||||||
|
|
||||||
# Try to receive same serial nos again in the same company with backdated.
|
# Try to receive same serial nos again in the same company with backdated.
|
||||||
pr1 = make_purchase_receipt(item_code=item.name, qty=2, rate=500,
|
pr1 = make_purchase_receipt(
|
||||||
posting_date=posting_date, serial_no='\n'.join(serial_nos), do_not_submit=True)
|
item_code=item.name,
|
||||||
|
qty=2,
|
||||||
|
rate=500,
|
||||||
|
posting_date=posting_date,
|
||||||
|
serial_no='\n'.join(serial_nos),
|
||||||
|
do_not_submit=True
|
||||||
|
)
|
||||||
|
|
||||||
self.assertRaises(SerialNoExistsInFutureTransaction, pr1.submit)
|
self.assertRaises(SerialNoExistsInFutureTransaction, pr1.submit)
|
||||||
|
|
||||||
# Try to receive same serial nos with different company with backdated.
|
# Try to receive same serial nos with different company with backdated.
|
||||||
pr2 = make_purchase_receipt(item_code=item.name, qty=2, rate=500,
|
pr2 = make_purchase_receipt(
|
||||||
posting_date=posting_date, serial_no='\n'.join(serial_nos), company='_Test Company 1', do_not_submit=True,
|
item_code=item.name,
|
||||||
warehouse = 'Stores - _TC1')
|
qty=2,
|
||||||
|
rate=500,
|
||||||
|
posting_date=posting_date,
|
||||||
|
serial_no='\n'.join(serial_nos),
|
||||||
|
company="_Test Company 1",
|
||||||
|
do_not_submit=True,
|
||||||
|
warehouse="Stores - _TC1"
|
||||||
|
)
|
||||||
|
|
||||||
self.assertRaises(SerialNoExistsInFutureTransaction, pr2.submit)
|
self.assertRaises(SerialNoExistsInFutureTransaction, pr2.submit)
|
||||||
|
|
||||||
# Receive the same serial nos after the delivery note posting date and time
|
# Receive the same serial nos after the delivery note posting date and time
|
||||||
make_purchase_receipt(item_code=item.name, qty=2, rate=500, serial_no='\n'.join(serial_nos))
|
make_purchase_receipt(
|
||||||
|
item_code=item.name,
|
||||||
|
qty=2,
|
||||||
|
rate=500,
|
||||||
|
serial_no='\n'.join(serial_nos)
|
||||||
|
)
|
||||||
|
|
||||||
# Raise the error for backdated deliver note entry cancel
|
# Raise the error for backdated deliver note entry cancel
|
||||||
self.assertRaises(SerialNoExistsInFutureTransaction, dn.cancel)
|
self.assertRaises(SerialNoExistsInFutureTransaction, dn.cancel)
|
||||||
@@ -238,11 +349,23 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
def test_subcontracting(self):
|
def test_subcontracting(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
|
||||||
|
|
||||||
frappe.db.set_value("Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM")
|
frappe.db.set_value(
|
||||||
make_stock_entry(item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100)
|
"Buying Settings", None,
|
||||||
make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse 1 - _TC",
|
"backflush_raw_materials_of_subcontract_based_on", "BOM"
|
||||||
qty=100, basic_rate=100)
|
)
|
||||||
pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=500, is_subcontracted="Yes")
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code="_Test Item", qty=100,
|
||||||
|
target="_Test Warehouse 1 - _TC", basic_rate=100
|
||||||
|
)
|
||||||
|
make_stock_entry(
|
||||||
|
item_code="_Test Item Home Desktop 100", qty=100,
|
||||||
|
target="_Test Warehouse 1 - _TC", basic_rate=100
|
||||||
|
)
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
item_code="_Test FG Item", qty=10,
|
||||||
|
rate=500, is_subcontracted="Yes"
|
||||||
|
)
|
||||||
self.assertEqual(len(pr.get("supplied_items")), 2)
|
self.assertEqual(len(pr.get("supplied_items")), 2)
|
||||||
|
|
||||||
rm_supp_cost = sum(d.amount for d in pr.get("supplied_items"))
|
rm_supp_cost = sum(d.amount for d in pr.get("supplied_items"))
|
||||||
@@ -252,17 +375,33 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
|
|
||||||
def test_subcontracting_gle_fg_item_rate_zero(self):
|
def test_subcontracting_gle_fg_item_rate_zero(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
|
||||||
frappe.db.set_value("Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM")
|
frappe.db.set_value(
|
||||||
|
"Buying Settings", None,
|
||||||
|
"backflush_raw_materials_of_subcontract_based_on", "BOM"
|
||||||
|
)
|
||||||
|
|
||||||
se1 = make_stock_entry(item_code="_Test Item", target="Work In Progress - TCP1",
|
se1 = make_stock_entry(
|
||||||
qty=100, basic_rate=100, company="_Test Company with perpetual inventory")
|
item_code="_Test Item",
|
||||||
|
target="Work In Progress - TCP1",
|
||||||
|
qty=100, basic_rate=100,
|
||||||
|
company="_Test Company with perpetual inventory"
|
||||||
|
)
|
||||||
|
|
||||||
se2 = make_stock_entry(item_code="_Test Item Home Desktop 100", target="Work In Progress - TCP1",
|
se2 = make_stock_entry(
|
||||||
qty=100, basic_rate=100, company="_Test Company with perpetual inventory")
|
item_code="_Test Item Home Desktop 100",
|
||||||
|
target="Work In Progress - TCP1",
|
||||||
|
qty=100, basic_rate=100,
|
||||||
|
company="_Test Company with perpetual inventory"
|
||||||
|
)
|
||||||
|
|
||||||
pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=0, is_subcontracted="Yes",
|
pr = make_purchase_receipt(
|
||||||
company="_Test Company with perpetual inventory", warehouse='Stores - TCP1',
|
item_code="_Test FG Item",
|
||||||
supplier_warehouse='Work In Progress - TCP1')
|
qty=10, rate=0,
|
||||||
|
is_subcontracted="Yes",
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
warehouse="Stores - TCP1",
|
||||||
|
supplier_warehouse="Work In Progress - TCP1"
|
||||||
|
)
|
||||||
|
|
||||||
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
|
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
|
||||||
|
|
||||||
@@ -293,16 +432,30 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
item_code = "_Test Subcontracted FG Item 1"
|
item_code = "_Test Subcontracted FG Item 1"
|
||||||
make_subcontracted_item(item_code=item_code)
|
make_subcontracted_item(item_code=item_code)
|
||||||
|
|
||||||
po = create_purchase_order(item_code=item_code, qty=1, include_exploded_items=0,
|
po = create_purchase_order(
|
||||||
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
item_code=item_code,
|
||||||
|
qty=1, include_exploded_items=0,
|
||||||
|
is_subcontracted="Yes",
|
||||||
|
supplier_warehouse="_Test Warehouse 1 - _TC"
|
||||||
|
)
|
||||||
|
|
||||||
|
# stock raw materials in a warehouse before transfer
|
||||||
|
se1 = make_stock_entry(
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
item_code="Test Extra Item 1",
|
||||||
|
qty=10, basic_rate=100
|
||||||
|
)
|
||||||
|
se2 = make_stock_entry(
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
item_code="_Test FG Item",
|
||||||
|
qty=1, basic_rate=100
|
||||||
|
)
|
||||||
|
se3 = make_stock_entry(
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
item_code="Test Extra Item 2",
|
||||||
|
qty=1, basic_rate=100
|
||||||
|
)
|
||||||
|
|
||||||
#stock raw materials in a warehouse before transfer
|
|
||||||
se1 = make_stock_entry(target="_Test Warehouse - _TC",
|
|
||||||
item_code = "Test Extra Item 1", qty=10, basic_rate=100)
|
|
||||||
se2 = make_stock_entry(target="_Test Warehouse - _TC",
|
|
||||||
item_code = "_Test FG Item", qty=1, basic_rate=100)
|
|
||||||
se3 = make_stock_entry(target="_Test Warehouse - _TC",
|
|
||||||
item_code = "Test Extra Item 2", qty=1, basic_rate=100)
|
|
||||||
rm_items = [
|
rm_items = [
|
||||||
{
|
{
|
||||||
"item_code": item_code,
|
"item_code": item_code,
|
||||||
@@ -350,11 +503,17 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
|
|
||||||
def test_serial_no_supplier(self):
|
def test_serial_no_supplier(self):
|
||||||
pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)
|
pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)
|
||||||
self.assertEqual(frappe.db.get_value("Serial No", pr.get("items")[0].serial_no, "supplier"),
|
pr_row_1_serial_no = pr.get("items")[0].serial_no
|
||||||
pr.supplier)
|
|
||||||
|
self.assertEqual(
|
||||||
|
frappe.db.get_value("Serial No", pr_row_1_serial_no, "supplier"),
|
||||||
|
pr.supplier
|
||||||
|
)
|
||||||
|
|
||||||
pr.cancel()
|
pr.cancel()
|
||||||
self.assertFalse(frappe.db.get_value("Serial No", pr.get("items")[0].serial_no, "warehouse"))
|
self.assertFalse(
|
||||||
|
frappe.db.get_value("Serial No", pr_row_1_serial_no, "warehouse")
|
||||||
|
)
|
||||||
|
|
||||||
def test_rejected_serial_no(self):
|
def test_rejected_serial_no(self):
|
||||||
pr = frappe.copy_doc(test_records[0])
|
pr = frappe.copy_doc(test_records[0])
|
||||||
@@ -381,18 +540,33 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
pr.cancel()
|
pr.cancel()
|
||||||
|
|
||||||
def test_purchase_return_partial(self):
|
def test_purchase_return_partial(self):
|
||||||
pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
|
pr = make_purchase_receipt(
|
||||||
warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1")
|
company="_Test Company with perpetual inventory",
|
||||||
|
warehouse = "Stores - TCP1",
|
||||||
|
supplier_warehouse = "Work in Progress - TCP1"
|
||||||
|
)
|
||||||
|
|
||||||
return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
|
return_pr = make_purchase_receipt(
|
||||||
warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1",
|
company="_Test Company with perpetual inventory",
|
||||||
is_return=1, return_against=pr.name, qty=-2, do_not_submit=1)
|
warehouse = "Stores - TCP1",
|
||||||
|
supplier_warehouse = "Work in Progress - TCP1",
|
||||||
|
is_return=1,
|
||||||
|
return_against=pr.name,
|
||||||
|
qty=-2,
|
||||||
|
do_not_submit=1
|
||||||
|
)
|
||||||
return_pr.items[0].purchase_receipt_item = pr.items[0].name
|
return_pr.items[0].purchase_receipt_item = pr.items[0].name
|
||||||
return_pr.submit()
|
return_pr.submit()
|
||||||
|
|
||||||
# check sle
|
# check sle
|
||||||
outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
|
outgoing_rate = frappe.db.get_value(
|
||||||
"voucher_no": return_pr.name}, "outgoing_rate")
|
"Stock Ledger Entry",
|
||||||
|
{
|
||||||
|
"voucher_type": "Purchase Receipt",
|
||||||
|
"voucher_no": return_pr.name
|
||||||
|
},
|
||||||
|
"outgoing_rate"
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(outgoing_rate, 50)
|
self.assertEqual(outgoing_rate, 50)
|
||||||
|
|
||||||
@@ -456,11 +630,21 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
pr.cancel()
|
pr.cancel()
|
||||||
|
|
||||||
def test_purchase_return_full(self):
|
def test_purchase_return_full(self):
|
||||||
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
|
pr = make_purchase_receipt(
|
||||||
supplier_warehouse = "Work in Progress - TCP1")
|
company="_Test Company with perpetual inventory",
|
||||||
|
warehouse = "Stores - TCP1",
|
||||||
|
supplier_warehouse = "Work in Progress - TCP1"
|
||||||
|
)
|
||||||
|
|
||||||
return_pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
|
return_pr = make_purchase_receipt(
|
||||||
supplier_warehouse = "Work in Progress - TCP1", is_return=1, return_against=pr.name, qty=-5, do_not_submit=1)
|
company="_Test Company with perpetual inventory",
|
||||||
|
warehouse = "Stores - TCP1",
|
||||||
|
supplier_warehouse = "Work in Progress - TCP1",
|
||||||
|
is_return=1,
|
||||||
|
return_against=pr.name,
|
||||||
|
qty=-5,
|
||||||
|
do_not_submit=1
|
||||||
|
)
|
||||||
return_pr.items[0].purchase_receipt_item = pr.items[0].name
|
return_pr.items[0].purchase_receipt_item = pr.items[0].name
|
||||||
return_pr.submit()
|
return_pr.submit()
|
||||||
|
|
||||||
@@ -482,15 +666,41 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
|
|
||||||
rejected_warehouse="_Test Rejected Warehouse - TCP1"
|
rejected_warehouse="_Test Rejected Warehouse - TCP1"
|
||||||
if not frappe.db.exists("Warehouse", rejected_warehouse):
|
if not frappe.db.exists("Warehouse", rejected_warehouse):
|
||||||
get_warehouse(company = "_Test Company with perpetual inventory",
|
get_warehouse(
|
||||||
abbr = " - TCP1", warehouse_name = "_Test Rejected Warehouse").name
|
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",
|
||||||
|
qty=2,
|
||||||
|
rejected_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,
|
||||||
|
qty=-2,
|
||||||
|
rejected_qty = -2,
|
||||||
|
rejected_warehouse=rejected_warehouse
|
||||||
|
)
|
||||||
|
|
||||||
actual_qty = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
|
actual_qty = frappe.db.get_value(
|
||||||
"voucher_no": return_pr.name, 'warehouse': return_pr.items[0].rejected_warehouse}, "actual_qty")
|
"Stock Ledger Entry",
|
||||||
|
{
|
||||||
|
"voucher_type": "Purchase Receipt",
|
||||||
|
"voucher_no": return_pr.name,
|
||||||
|
"warehouse": return_pr.items[0].rejected_warehouse
|
||||||
|
},
|
||||||
|
"actual_qty"
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(actual_qty, -2)
|
self.assertEqual(actual_qty, -2)
|
||||||
|
|
||||||
@@ -515,8 +725,13 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
"purchase_document_no": pr.name
|
"purchase_document_no": pr.name
|
||||||
})
|
})
|
||||||
|
|
||||||
return_pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=-1,
|
return_pr = make_purchase_receipt(
|
||||||
is_return=1, return_against=pr.name, serial_no=serial_no)
|
item_code="_Test Serialized Item With Series",
|
||||||
|
qty=-1,
|
||||||
|
is_return=1,
|
||||||
|
return_against=pr.name,
|
||||||
|
serial_no=serial_no
|
||||||
|
)
|
||||||
|
|
||||||
_check_serial_no_values(serial_no, {
|
_check_serial_no_values(serial_no, {
|
||||||
"warehouse": "",
|
"warehouse": "",
|
||||||
@@ -538,9 +753,21 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
})
|
})
|
||||||
row.db_update()
|
row.db_update()
|
||||||
|
|
||||||
pr = make_purchase_receipt(item_code=item_code, qty=1, uom="Box", conversion_factor=1.0)
|
pr = make_purchase_receipt(
|
||||||
return_pr = make_purchase_receipt(item_code=item_code, qty=-10, uom="Unit",
|
item_code=item_code,
|
||||||
stock_uom="Box", conversion_factor=0.1, is_return=1, return_against=pr.name)
|
qty=1,
|
||||||
|
uom="Box",
|
||||||
|
conversion_factor=1.0
|
||||||
|
)
|
||||||
|
return_pr = make_purchase_receipt(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=-10,
|
||||||
|
uom="Unit",
|
||||||
|
stock_uom="Box",
|
||||||
|
conversion_factor=0.1,
|
||||||
|
is_return=1,
|
||||||
|
return_against=pr.name
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(abs(return_pr.items[0].stock_qty), 1.0)
|
self.assertEqual(abs(return_pr.items[0].stock_qty), 1.0)
|
||||||
|
|
||||||
@@ -556,13 +783,19 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
pr.submit()
|
pr.submit()
|
||||||
|
|
||||||
update_purchase_receipt_status(pr.name, "Closed")
|
update_purchase_receipt_status(pr.name, "Closed")
|
||||||
self.assertEqual(frappe.db.get_value("Purchase Receipt", pr.name, "status"), "Closed")
|
self.assertEqual(
|
||||||
|
frappe.db.get_value("Purchase Receipt", pr.name, "status"), "Closed"
|
||||||
|
)
|
||||||
|
|
||||||
pr.reload()
|
pr.reload()
|
||||||
pr.cancel()
|
pr.cancel()
|
||||||
|
|
||||||
def test_pr_billing_status(self):
|
def test_pr_billing_status(self):
|
||||||
# PO -> PR1 -> PI and PO -> PI and PO -> PR2
|
"""Flow:
|
||||||
|
1. PO -> PR1 -> PI
|
||||||
|
2. PO -> PI
|
||||||
|
3. PO -> PR2.
|
||||||
|
"""
|
||||||
from erpnext.buying.doctype.purchase_order.purchase_order import (
|
from erpnext.buying.doctype.purchase_order.purchase_order import (
|
||||||
make_purchase_invoice as make_purchase_invoice_from_po,
|
make_purchase_invoice as make_purchase_invoice_from_po,
|
||||||
)
|
)
|
||||||
@@ -626,21 +859,39 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
pr_doc = make_purchase_receipt(item_code=item_code,
|
pr_doc = make_purchase_receipt(item_code=item_code,
|
||||||
qty=1, serial_no = serial_no)
|
qty=1, serial_no = serial_no)
|
||||||
|
|
||||||
self.assertEqual(serial_no, frappe.db.get_value("Serial No",
|
self.assertEqual(
|
||||||
{"purchase_document_type": "Purchase Receipt", "purchase_document_no": pr_doc.name}, "name"))
|
serial_no,
|
||||||
|
frappe.db.get_value(
|
||||||
|
"Serial No",
|
||||||
|
{
|
||||||
|
"purchase_document_type": "Purchase Receipt",
|
||||||
|
"purchase_document_no": pr_doc.name
|
||||||
|
},
|
||||||
|
"name"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
pr_doc.cancel()
|
pr_doc.cancel()
|
||||||
|
|
||||||
#check for the auto created serial nos
|
# check for the auto created serial nos
|
||||||
item_code = "Test Auto Created Serial No"
|
item_code = "Test Auto Created Serial No"
|
||||||
if not frappe.db.exists("Item", item_code):
|
if not frappe.db.exists("Item", item_code):
|
||||||
item = make_item(item_code, dict(has_serial_no=1, serial_no_series="KLJL.###"))
|
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)
|
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]
|
serial_no = get_serial_nos(new_pr_doc.items[0].serial_no)[0]
|
||||||
self.assertEqual(serial_no, frappe.db.get_value("Serial No",
|
self.assertEqual(
|
||||||
{"purchase_document_type": "Purchase Receipt", "purchase_document_no": new_pr_doc.name}, "name"))
|
serial_no,
|
||||||
|
frappe.db.get_value(
|
||||||
|
"Serial No",
|
||||||
|
{
|
||||||
|
"purchase_document_type": "Purchase Receipt",
|
||||||
|
"purchase_document_no": new_pr_doc.name
|
||||||
|
},
|
||||||
|
"name"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
new_pr_doc.cancel()
|
new_pr_doc.cancel()
|
||||||
|
|
||||||
@@ -716,8 +967,12 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
|
|
||||||
def test_purchase_receipt_cost_center(self):
|
def test_purchase_receipt_cost_center(self):
|
||||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||||
|
|
||||||
cost_center = "_Test Cost Center for BS Account - TCP1"
|
cost_center = "_Test Cost Center for BS Account - TCP1"
|
||||||
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company with perpetual inventory")
|
create_cost_center(
|
||||||
|
cost_center_name="_Test Cost Center for BS Account",
|
||||||
|
company="_Test Company with perpetual inventory"
|
||||||
|
)
|
||||||
|
|
||||||
if not frappe.db.exists('Location', 'Test Location'):
|
if not frappe.db.exists('Location', 'Test Location'):
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
@@ -725,10 +980,16 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
'location_name': 'Test Location'
|
'location_name': 'Test Location'
|
||||||
}).insert()
|
}).insert()
|
||||||
|
|
||||||
pr = make_purchase_receipt(cost_center=cost_center, company="_Test Company with perpetual inventory",
|
pr = make_purchase_receipt(
|
||||||
warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1")
|
cost_center=cost_center,
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
warehouse = "Stores - TCP1",
|
||||||
|
supplier_warehouse = "Work in Progress - TCP1"
|
||||||
|
)
|
||||||
|
|
||||||
stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse)
|
stock_in_hand_account = get_inventory_account(
|
||||||
|
pr.company, pr.get("items")[0].warehouse
|
||||||
|
)
|
||||||
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
|
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
|
||||||
|
|
||||||
self.assertTrue(gl_entries)
|
self.assertTrue(gl_entries)
|
||||||
@@ -752,9 +1013,16 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
'doctype': 'Location',
|
'doctype': 'Location',
|
||||||
'location_name': 'Test Location'
|
'location_name': 'Test Location'
|
||||||
}).insert()
|
}).insert()
|
||||||
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1")
|
|
||||||
|
|
||||||
stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse)
|
pr = make_purchase_receipt(
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
warehouse = "Stores - TCP1",
|
||||||
|
supplier_warehouse = "Work in Progress - TCP1"
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_in_hand_account = get_inventory_account(
|
||||||
|
pr.company, pr.get("items")[0].warehouse
|
||||||
|
)
|
||||||
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
|
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
|
||||||
|
|
||||||
self.assertTrue(gl_entries)
|
self.assertTrue(gl_entries)
|
||||||
@@ -782,7 +1050,11 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
po = create_purchase_order()
|
po = create_purchase_order()
|
||||||
pr = create_pr_against_po(po.name)
|
pr = create_pr_against_po(po.name)
|
||||||
|
|
||||||
pr1 = make_purchase_receipt(is_return=1, return_against=pr.name, qty=-1, do_not_submit=True)
|
pr1 = make_purchase_receipt(
|
||||||
|
qty=-1,
|
||||||
|
is_return=1, return_against=pr.name,
|
||||||
|
do_not_submit=True
|
||||||
|
)
|
||||||
pr1.items[0].purchase_order = po.name
|
pr1.items[0].purchase_order = po.name
|
||||||
pr1.items[0].purchase_order_item = po.items[0].name
|
pr1.items[0].purchase_order_item = po.items[0].name
|
||||||
pr1.items[0].purchase_receipt_item = pr.items[0].name
|
pr1.items[0].purchase_receipt_item = pr.items[0].name
|
||||||
@@ -815,7 +1087,11 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
pi1.save()
|
pi1.save()
|
||||||
pi1.submit()
|
pi1.submit()
|
||||||
|
|
||||||
pr2 = make_purchase_receipt(is_return=1, return_against=pr1.name, qty=-2, do_not_submit=True)
|
pr2 = make_purchase_receipt(
|
||||||
|
qty=-2,
|
||||||
|
is_return=1, return_against=pr1.name,
|
||||||
|
do_not_submit=True
|
||||||
|
)
|
||||||
pr2.items[0].purchase_receipt_item = pr1.items[0].name
|
pr2.items[0].purchase_receipt_item = pr1.items[0].name
|
||||||
pr2.submit()
|
pr2.submit()
|
||||||
|
|
||||||
@@ -857,14 +1133,22 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
pr1.cancel()
|
pr1.cancel()
|
||||||
|
|
||||||
def test_stock_transfer_from_purchase_receipt_with_valuation(self):
|
def test_stock_transfer_from_purchase_receipt_with_valuation(self):
|
||||||
create_warehouse("_Test Warehouse for Valuation", company="_Test Company with perpetual inventory",
|
create_warehouse(
|
||||||
properties={"account": '_Test Account Stock In Hand - TCP1'})
|
"_Test Warehouse for Valuation",
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
properties={"account": '_Test Account Stock In Hand - TCP1'}
|
||||||
|
)
|
||||||
|
|
||||||
pr1 = make_purchase_receipt(warehouse = '_Test Warehouse for Valuation - TCP1',
|
pr1 = make_purchase_receipt(
|
||||||
company="_Test Company with perpetual inventory")
|
warehouse = '_Test Warehouse for Valuation - TCP1',
|
||||||
|
company="_Test Company with perpetual inventory"
|
||||||
|
)
|
||||||
|
|
||||||
pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
|
pr = make_purchase_receipt(
|
||||||
warehouse = "Stores - TCP1", do_not_save=1)
|
company="_Test Company with perpetual inventory",
|
||||||
|
warehouse = "Stores - TCP1",
|
||||||
|
do_not_save=1
|
||||||
|
)
|
||||||
|
|
||||||
pr.items[0].from_warehouse = '_Test Warehouse for Valuation - TCP1'
|
pr.items[0].from_warehouse = '_Test Warehouse for Valuation - TCP1'
|
||||||
pr.supplier_warehouse = ''
|
pr.supplier_warehouse = ''
|
||||||
@@ -946,10 +1230,24 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
rm_items = [
|
rm_items = [
|
||||||
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 3","item_name":"_Test Item",
|
{
|
||||||
"qty":300,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos", "name": po.supplied_items[0].name},
|
"item_code":item_code,
|
||||||
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 3","item_name":"_Test Item",
|
"rm_item_code":"Sub Contracted Raw Material 3",
|
||||||
"qty":200,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos", "name": po.supplied_items[0].name}
|
"item_name":"_Test Item",
|
||||||
|
"qty":300,
|
||||||
|
"warehouse":"_Test Warehouse - _TC",
|
||||||
|
"stock_uom":"Nos",
|
||||||
|
"name": po.supplied_items[0].name
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_code":item_code,
|
||||||
|
"rm_item_code":"Sub Contracted Raw Material 3",
|
||||||
|
"item_name":"_Test Item",
|
||||||
|
"qty":200,
|
||||||
|
"warehouse":"_Test Warehouse - _TC",
|
||||||
|
"stock_uom":"Nos",
|
||||||
|
"name": po.supplied_items[0].name
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
rm_item_string = json.dumps(rm_items)
|
rm_item_string = json.dumps(rm_items)
|
||||||
@@ -959,8 +1257,14 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
se.items[1].batch_no = ste2.items[0].batch_no
|
se.items[1].batch_no = ste2.items[0].batch_no
|
||||||
se.submit()
|
se.submit()
|
||||||
|
|
||||||
supplied_qty = frappe.db.get_value("Purchase Order Item Supplied",
|
supplied_qty = frappe.db.get_value(
|
||||||
{"parent": po.name, "rm_item_code": "Sub Contracted Raw Material 3"}, "supplied_qty")
|
"Purchase Order Item Supplied",
|
||||||
|
{
|
||||||
|
"parent": po.name,
|
||||||
|
"rm_item_code": "Sub Contracted Raw Material 3"
|
||||||
|
},
|
||||||
|
"supplied_qty"
|
||||||
|
)
|
||||||
|
|
||||||
self.assertEqual(supplied_qty, 500.00)
|
self.assertEqual(supplied_qty, 500.00)
|
||||||
|
|
||||||
@@ -1032,10 +1336,18 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
company = '_Test Company with perpetual inventory'
|
company = '_Test Company with perpetual inventory'
|
||||||
service_item = '_Test Non Stock Item'
|
service_item = '_Test Non Stock Item'
|
||||||
|
|
||||||
before_test_value = frappe.db.get_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items')
|
before_test_value = frappe.db.get_value(
|
||||||
frappe.db.set_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items', 1)
|
'Company', company, 'enable_perpetual_inventory_for_non_stock_items'
|
||||||
|
)
|
||||||
|
frappe.db.set_value(
|
||||||
|
'Company', company,
|
||||||
|
'enable_perpetual_inventory_for_non_stock_items', 1
|
||||||
|
)
|
||||||
srbnb_account = 'Stock Received But Not Billed - TCP1'
|
srbnb_account = 'Stock Received But Not Billed - TCP1'
|
||||||
frappe.db.set_value('Company', company, 'service_received_but_not_billed', srbnb_account)
|
frappe.db.set_value(
|
||||||
|
'Company', company,
|
||||||
|
'service_received_but_not_billed', srbnb_account
|
||||||
|
)
|
||||||
|
|
||||||
pr = make_purchase_receipt(
|
pr = make_purchase_receipt(
|
||||||
company=company, item=service_item,
|
company=company, item=service_item,
|
||||||
@@ -1067,7 +1379,10 @@ class TestPurchaseReceipt(ERPNextTestCase):
|
|||||||
self.assertEqual(len(item_one_gl_entry), 1)
|
self.assertEqual(len(item_one_gl_entry), 1)
|
||||||
self.assertEqual(len(item_two_gl_entry), 1)
|
self.assertEqual(len(item_two_gl_entry), 1)
|
||||||
|
|
||||||
frappe.db.set_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items', before_test_value)
|
frappe.db.set_value(
|
||||||
|
'Company', company,
|
||||||
|
'enable_perpetual_inventory_for_non_stock_items', before_test_value
|
||||||
|
)
|
||||||
|
|
||||||
def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
|
def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
|
||||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
|
||||||
@@ -1211,8 +1526,8 @@ def make_purchase_receipt(**args):
|
|||||||
pr.return_against = args.return_against
|
pr.return_against = args.return_against
|
||||||
pr.apply_putaway_rule = args.apply_putaway_rule
|
pr.apply_putaway_rule = args.apply_putaway_rule
|
||||||
qty = args.qty or 5
|
qty = args.qty or 5
|
||||||
received_qty = args.received_qty or qty
|
rejected_qty = args.rejected_qty or 0
|
||||||
rejected_qty = args.rejected_qty or flt(received_qty) - flt(qty)
|
received_qty = args.received_qty or flt(rejected_qty) + flt(qty)
|
||||||
|
|
||||||
item_code = args.item or args.item_code or "_Test Item"
|
item_code = args.item or args.item_code or "_Test Item"
|
||||||
uom = args.uom or frappe.db.get_value("Item", item_code, "stock_uom") or "_Test UOM"
|
uom = args.uom or frappe.db.get_value("Item", item_code, "stock_uom") or "_Test UOM"
|
||||||
@@ -1235,9 +1550,12 @@ def make_purchase_receipt(**args):
|
|||||||
|
|
||||||
if args.get_multiple_items:
|
if args.get_multiple_items:
|
||||||
pr.items = []
|
pr.items = []
|
||||||
for item in get_items(warehouse= args.warehouse, cost_center = args.cost_center or frappe.get_cached_value('Company', pr.company, 'cost_center')):
|
|
||||||
pr.append("items", item)
|
|
||||||
|
|
||||||
|
company_cost_center = frappe.get_cached_value('Company', pr.company, 'cost_center')
|
||||||
|
cost_center = args.cost_center or company_cost_center
|
||||||
|
|
||||||
|
for item in get_items(warehouse=args.warehouse, cost_center=cost_center):
|
||||||
|
pr.append("items", item)
|
||||||
|
|
||||||
if args.get_taxes_and_charges:
|
if args.get_taxes_and_charges:
|
||||||
for tax in get_taxes():
|
for tax in get_taxes():
|
||||||
|
|||||||
@@ -197,6 +197,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bold": 1,
|
"bold": 1,
|
||||||
|
"default": "0",
|
||||||
"fieldname": "received_qty",
|
"fieldname": "received_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Received Quantity",
|
"label": "Received Quantity",
|
||||||
@@ -204,6 +205,7 @@
|
|||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_width": "100px",
|
"print_width": "100px",
|
||||||
|
"read_only": 1,
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
@@ -219,8 +221,10 @@
|
|||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"columns": 1,
|
||||||
"fieldname": "rejected_qty",
|
"fieldname": "rejected_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Rejected Quantity",
|
"label": "Rejected Quantity",
|
||||||
"oldfieldname": "rejected_qty",
|
"oldfieldname": "rejected_qty",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
@@ -327,7 +331,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bold": 1,
|
"bold": 1,
|
||||||
"columns": 3,
|
"columns": 2,
|
||||||
"fieldname": "rate",
|
"fieldname": "rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -543,6 +547,7 @@
|
|||||||
"fieldname": "stock_qty",
|
"fieldname": "stock_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Accepted Qty in Stock UOM",
|
"label": "Accepted Qty in Stock UOM",
|
||||||
|
"no_copy": 1,
|
||||||
"oldfieldname": "stock_qty",
|
"oldfieldname": "stock_qty",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
@@ -882,7 +887,9 @@
|
|||||||
"fieldname": "received_stock_qty",
|
"fieldname": "received_stock_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Received Qty in Stock UOM",
|
"label": "Received Qty in Stock UOM",
|
||||||
"print_hide": 1
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval: doc.uom != doc.stock_uom",
|
"depends_on": "eval: doc.uom != doc.stock_uom",
|
||||||
@@ -969,10 +976,11 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-09-01 16:02:40.338597",
|
"modified": "2021-11-15 15:46:10.591600",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt Item",
|
"name": "Purchase Receipt Item",
|
||||||
|
"naming_rule": "Random",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ def check_serial_no_validity_on_cancel(serial_no, sle):
|
|||||||
is_stock_reco = sle.voucher_type == "Stock Reconciliation"
|
is_stock_reco = sle.voucher_type == "Stock Reconciliation"
|
||||||
msg = None
|
msg = None
|
||||||
|
|
||||||
if sr and (actual_qty < 0 or is_stock_reco) and sr.warehouse != sle.warehouse:
|
if sr and (actual_qty < 0 or is_stock_reco) and (sr.warehouse and sr.warehouse != sle.warehouse):
|
||||||
# receipt(inward) is being cancelled
|
# receipt(inward) is being cancelled
|
||||||
msg = _("Cannot cancel {0} {1} as Serial No {2} does not belong to the warehouse {3}").format(
|
msg = _("Cannot cancel {0} {1} as Serial No {2} does not belong to the warehouse {3}").format(
|
||||||
sle.voucher_type, doc_link, sr_link, frappe.bold(sle.warehouse))
|
sle.voucher_type, doc_link, sr_link, frappe.bold(sle.warehouse))
|
||||||
|
|||||||
@@ -400,6 +400,34 @@ class TestStockReconciliation(ERPNextTestCase):
|
|||||||
, do_not_submit=True)
|
, do_not_submit=True)
|
||||||
self.assertRaises(frappe.ValidationError, sr.submit)
|
self.assertRaises(frappe.ValidationError, sr.submit)
|
||||||
|
|
||||||
|
def test_serial_no_cancellation(self):
|
||||||
|
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
item = create_item("Stock-Reco-Serial-Item-9", is_stock_item=1)
|
||||||
|
if not item.has_serial_no:
|
||||||
|
item.has_serial_no = 1
|
||||||
|
item.serial_no_series = "SRS9.####"
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
item_code = item.name
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
|
||||||
|
se1 = make_stock_entry(item_code=item_code, target=warehouse, qty=10, basic_rate=700)
|
||||||
|
|
||||||
|
serial_nos = get_serial_nos(se1.items[0].serial_no)
|
||||||
|
# reduce 1 item
|
||||||
|
serial_nos.pop()
|
||||||
|
new_serial_nos = "\n".join(serial_nos)
|
||||||
|
|
||||||
|
sr = create_stock_reconciliation(item_code=item.name, warehouse=warehouse, serial_no=new_serial_nos, qty=9)
|
||||||
|
sr.cancel()
|
||||||
|
|
||||||
|
active_sr_no = frappe.get_all("Serial No",
|
||||||
|
filters={"item_code": item_code, "warehouse": warehouse, "status": "Active"})
|
||||||
|
|
||||||
|
self.assertEqual(len(active_sr_no), 10)
|
||||||
|
|
||||||
|
|
||||||
def create_batch_item_with_batch(item_name, batch_id):
|
def create_batch_item_with_batch(item_name, batch_id):
|
||||||
batch_item_doc = create_item(item_name, is_stock_item=1)
|
batch_item_doc = create_item(item_name, is_stock_item=1)
|
||||||
if not batch_item_doc.has_batch_no:
|
if not batch_item_doc.has_batch_no:
|
||||||
|
|||||||
Reference in New Issue
Block a user