Merge branch 'develop' into fix_picked_qty_in_DN

This commit is contained in:
Marica 2021-04-02 13:19:19 +05:30 committed by GitHub
commit 7f9fedf08a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 301 additions and 92 deletions

View File

@ -43,7 +43,7 @@
}
],
"grand_total": 0,
"naming_series": "_T-BILL",
"naming_series": "T-PINV-",
"taxes": [
{
"account_head": "_Test Account Shipping Charges - _TC",
@ -167,7 +167,7 @@
}
],
"grand_total": 0,
"naming_series": "_T-Purchase Invoice-",
"naming_series": "T-PINV-",
"taxes": [
{
"account_head": "_Test Account Shipping Charges - _TC",

View File

@ -31,7 +31,7 @@
"base_grand_total": 561.8,
"grand_total": 561.8,
"is_pos": 0,
"naming_series": "_T-Sales Invoice-",
"naming_series": "T-SINV-",
"base_net_total": 500.0,
"taxes": [
{
@ -104,7 +104,7 @@
"base_grand_total": 630.0,
"grand_total": 630.0,
"is_pos": 0,
"naming_series": "_T-Sales Invoice-",
"naming_series": "T-SINV-",
"base_net_total": 500.0,
"taxes": [
{
@ -175,7 +175,7 @@
],
"grand_total": 0,
"is_pos": 0,
"naming_series": "_T-Sales Invoice-",
"naming_series": "T-SINV-",
"taxes": [
{
"account_head": "_Test Account Shipping Charges - _TC",
@ -301,7 +301,7 @@
],
"grand_total": 0,
"is_pos": 0,
"naming_series": "_T-Sales Invoice-",
"naming_series": "T-SINV-",
"taxes": [
{
"account_head": "_Test Account Excise Duty - _TC",

View File

@ -2115,6 +2115,7 @@ def create_sales_invoice(**args):
si.return_against = args.return_against
si.currency=args.currency or "INR"
si.conversion_rate = args.conversion_rate or 1
si.naming_series = args.naming_series or "T-SINV-"
si.append("items", {
"item_code": args.item or args.item_code or "_Test Item",

View File

@ -253,6 +253,7 @@ class PurchaseOrder(BuyingController):
self.update_prevdoc_status()
# Must be called after updating ordered qty in Material Request
# bin uses Material Request Items to recalculate & update
self.update_requested_qty()
self.update_ordered_qty()

View File

@ -90,6 +90,50 @@ class TestPurchaseOrder(unittest.TestCase):
frappe.db.set_value('Item', '_Test Item', 'over_billing_allowance', 0)
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0)
def test_update_remove_child_linked_to_mr(self):
"""Test impact on linked PO and MR on deleting/updating row."""
mr = make_material_request(qty=10)
po = make_purchase_order(mr.name)
po.supplier = "_Test Supplier"
po.save()
po.submit()
first_item_of_po = po.get("items")[0]
existing_ordered_qty = get_ordered_qty() # 10
existing_requested_qty = get_requested_qty() # 0
# decrease ordered qty by 3 (10 -> 7) and add item
trans_item = json.dumps([
{
'item_code': first_item_of_po.item_code,
'rate': first_item_of_po.rate,
'qty': 7,
'docname': first_item_of_po.name
},
{'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 2}
])
update_child_qty_rate('Purchase Order', trans_item, po.name)
mr.reload()
# requested qty increases as ordered qty decreases
self.assertEqual(get_requested_qty(), existing_requested_qty + 3) # 3
self.assertEqual(mr.items[0].ordered_qty, 7)
self.assertEqual(get_ordered_qty(), existing_ordered_qty - 3) # 7
# delete first item linked to Material Request
trans_item = json.dumps([
{'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 2}
])
update_child_qty_rate('Purchase Order', trans_item, po.name)
mr.reload()
# requested qty increases as ordered qty is 0 (deleted row)
self.assertEqual(get_requested_qty(), existing_requested_qty + 10) # 10
self.assertEqual(mr.items[0].ordered_qty, 0)
# ordered qty decreases as ordered qty is 0 (deleted row)
self.assertEqual(get_ordered_qty(), existing_ordered_qty - 10) # 0
def test_update_child(self):
mr = make_material_request(qty=10)
@ -120,7 +164,6 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEqual(po.get("items")[0].amount, 1400)
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
def test_update_child_adding_new_item(self):
po = create_purchase_order(do_not_save=1)
po.items[0].qty = 4
@ -129,6 +172,7 @@ class TestPurchaseOrder(unittest.TestCase):
pr = make_pr_against_po(po.name, 2)
po.load_from_db()
existing_ordered_qty = get_ordered_qty()
first_item_of_po = po.get("items")[0]
trans_item = json.dumps([
@ -145,7 +189,8 @@ class TestPurchaseOrder(unittest.TestCase):
po.reload()
self.assertEquals(len(po.get('items')), 2)
self.assertEqual(po.status, 'To Receive and Bill')
# ordered qty should increase on row addition
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7)
def test_update_child_removing_item(self):
po = create_purchase_order(do_not_save=1)
@ -156,6 +201,7 @@ class TestPurchaseOrder(unittest.TestCase):
po.reload()
first_item_of_po = po.get("items")[0]
existing_ordered_qty = get_ordered_qty()
# add an item
trans_item = json.dumps([
{
@ -168,6 +214,10 @@ class TestPurchaseOrder(unittest.TestCase):
update_child_qty_rate('Purchase Order', trans_item, po.name)
po.reload()
# ordered qty should increase on row addition
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7)
# check if can remove received item
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.get("items")[1].name}])
self.assertRaises(frappe.ValidationError, update_child_qty_rate, 'Purchase Order', trans_item, po.name)
@ -187,6 +237,9 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEquals(len(po.get('items')), 1)
self.assertEqual(po.status, 'To Receive and Bill')
# ordered qty should decrease (back to initial) on row deletion
self.assertEqual(get_ordered_qty(), existing_ordered_qty)
def test_update_child_perm(self):
po = create_purchase_order(item_code= "_Test Item", qty=4)
@ -230,11 +283,13 @@ class TestPurchaseOrder(unittest.TestCase):
new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax")
new_item_with_tax.append("taxes", {
"item_tax_template": "Test Update Items Template - _TC",
"valid_from": nowdate()
})
new_item_with_tax.save()
if not frappe.db.exists("Item Tax",
{"item_tax_template": "Test Update Items Template - _TC", "parent": "Test Item with Tax"}):
new_item_with_tax.append("taxes", {
"item_tax_template": "Test Update Items Template - _TC",
"valid_from": nowdate()
})
new_item_with_tax.save()
tax_template = "_Test Account Excise Duty @ 10 - _TC"
item = "_Test Item Home Desktop 100"

View File

@ -9,9 +9,7 @@ import unittest
class TestSupplierScorecard(unittest.TestCase):
def test_create_scorecard(self):
delete_test_scorecards()
my_doc = make_supplier_scorecard()
doc = my_doc.insert()
doc = make_supplier_scorecard().insert()
self.assertEqual(doc.name, valid_scorecard[0].get("supplier"))
def test_criteria_weight(self):
@ -121,7 +119,8 @@ valid_scorecard = [
{
"weight":100.0,
"doctype":"Supplier Scorecard Scoring Criteria",
"criteria_name":"Delivery"
"criteria_name":"Delivery",
"formula": "100"
}
],
"supplier":"_Test Supplier",

View File

@ -1336,25 +1336,63 @@ def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child
p_doc = frappe.get_doc(parent_doctype, parent_doctype_name)
child_item = frappe.new_doc(child_doctype, p_doc, child_docname)
item = frappe.get_doc("Item", trans_item.get('item_code'))
for field in ("item_code", "item_name", "description", "item_group"):
child_item.update({field: item.get(field)})
child_item.update({field: item.get(field)})
date_fieldname = "delivery_date" if child_doctype == "Sales Order Item" else "schedule_date"
child_item.update({date_fieldname: trans_item.get(date_fieldname) or p_doc.get(date_fieldname)})
child_item.stock_uom = item.stock_uom
child_item.uom = trans_item.get("uom") or item.stock_uom
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
if child_doctype == "Purchase Order Item":
child_item.base_rate = 1 # Initiallize value will update in parent validation
child_item.base_amount = 1 # Initiallize value will update in parent validation
# Initialized value will update in parent validation
child_item.base_rate = 1
child_item.base_amount = 1
if child_doctype == "Sales Order Item":
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
if not child_item.warehouse:
frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
.format(frappe.bold("default warehouse"), frappe.bold(item.item_code)))
set_child_tax_template_and_map(item, child_item, p_doc)
add_taxes_from_tax_template(child_item, p_doc)
return child_item
def validate_child_on_delete(row, parent):
"""Check if partially transacted item (row) is being deleted."""
if parent.doctype == "Sales Order":
if flt(row.delivered_qty):
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(row.idx, row.item_code))
if flt(row.work_order_qty):
frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(row.idx, row.item_code))
if flt(row.ordered_qty):
frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(row.idx, row.item_code))
if parent.doctype == "Purchase Order" and flt(row.received_qty):
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(row.idx, row.item_code))
if flt(row.billed_amt):
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(row.idx, row.item_code))
def update_bin_on_delete(row, doctype):
"""Update bin for deleted item (row)."""
from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty, get_ordered_qty, get_indented_qty
qty_dict = {}
if doctype == "Sales Order":
qty_dict["reserved_qty"] = get_reserved_qty(row.item_code, row.warehouse)
else:
if row.material_request_item:
qty_dict["indented_qty"] = get_indented_qty(row.item_code, row.warehouse)
qty_dict["ordered_qty"] = get_ordered_qty(row.item_code, row.warehouse)
update_bin_qty(row.item_code, row.warehouse, qty_dict)
def validate_and_delete_children(parent, data):
deleted_children = []
updated_item_names = [d.get("docname") for d in data]
@ -1363,23 +1401,17 @@ def validate_and_delete_children(parent, data):
deleted_children.append(item)
for d in deleted_children:
if parent.doctype == "Sales Order":
if flt(d.delivered_qty):
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(d.idx, d.item_code))
if flt(d.work_order_qty):
frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(d.idx, d.item_code))
if flt(d.ordered_qty):
frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(d.idx, d.item_code))
if parent.doctype == "Purchase Order" and flt(d.received_qty):
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(d.idx, d.item_code))
if flt(d.billed_amt):
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(d.idx, d.item_code))
validate_child_on_delete(d, parent)
d.cancel()
d.delete()
# need to update ordered qty in Material Request first
# bin uses Material Request Items to recalculate & update
parent.update_prevdoc_status()
for d in deleted_children:
update_bin_on_delete(d, parent.doctype)
@frappe.whitelist()
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
def check_doc_permissions(doc, perm_type='create'):

View File

@ -181,7 +181,6 @@
"read_only": 1
},
{
"default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
"fieldname": "currency",
"fieldtype": "Link",
@ -201,7 +200,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-11-25 12:01:55.980721",
"modified": "2021-03-31 14:42:47.321368",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Advance",

View File

@ -130,7 +130,6 @@
"read_only": 1
},
{
"default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
"fieldname": "currency",
"fieldtype": "Link",
@ -155,7 +154,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-11-25 11:56:06.777241",
"modified": "2021-03-31 14:45:27.948207",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Encashment",

View File

@ -25,6 +25,16 @@ frappe.ui.form.on('Production Plan', {
}
});
frm.set_query('material_request', 'material_requests', function() {
return {
filters: {
material_request_type: "Manufacture",
docstatus: 1,
status: ["!=", "Stopped"],
}
};
});
frm.fields_dict['po_items'].grid.get_field('item_code').get_query = function(doc) {
return {
query: "erpnext.controllers.queries.item_query",
@ -370,4 +380,4 @@ cur_frm.fields_dict['sales_orders'].grid.get_field("sales_order").get_query = fu
['Sales Order','docstatus', '=' ,1]
]
}
};
};

View File

@ -70,7 +70,7 @@ class ProductionPlan(Document):
from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
where mr_item.parent = mr.name
and mr.material_request_type = "Manufacture"
and mr.docstatus = 1 and mr.company = %(company)s
and mr.docstatus = 1 and mr.status != "Stopped" and mr.company = %(company)s
and mr_item.qty > ifnull(mr_item.ordered_qty,0) {0} {1}
and (exists (select name from `tabBOM` bom where bom.item=mr_item.item_code
and bom.is_active = 1))

View File

@ -99,7 +99,7 @@ execute:frappe.delete_doc("DocType", "Purchase Request")
execute:frappe.delete_doc("DocType", "Purchase Request Item")
erpnext.patches.v4_2.recalculate_bom_cost
erpnext.patches.v4_2.fix_gl_entries_for_stock_transactions
erpnext.patches.v4_2.update_requested_and_ordered_qty
erpnext.patches.v4_2.update_requested_and_ordered_qty #2021-03-31
execute:frappe.rename_doc("DocType", "Support Ticket", "Issue", force=True)
erpnext.patches.v4_4.make_email_accounts
execute:frappe.delete_doc("DocType", "Contact Control")
@ -208,7 +208,7 @@ erpnext.patches.v5_7.update_item_description_based_on_item_master
erpnext.patches.v5_7.item_template_attributes
execute:frappe.delete_doc_if_exists("DocType", "Manage Variants")
execute:frappe.delete_doc_if_exists("DocType", "Manage Variants Item")
erpnext.patches.v4_2.repost_reserved_qty #2016-04-15
erpnext.patches.v4_2.repost_reserved_qty #2021-03-31
erpnext.patches.v5_4.update_purchase_cost_against_project
erpnext.patches.v5_8.update_order_reference_in_return_entries
erpnext.patches.v5_8.add_credit_note_print_heading

View File

@ -163,7 +163,6 @@
"read_only": 1
},
{
"default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
"fieldname": "currency",
"fieldtype": "Link",
@ -176,7 +175,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-10-20 17:51:13.419716",
"modified": "2021-03-31 14:45:48.566756",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Additional Salary",

View File

@ -124,7 +124,6 @@
"read_only": 1
},
{
"default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
"fieldname": "currency",
"fieldtype": "Link",
@ -148,7 +147,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-12-14 15:52:08.566418",
"modified": "2021-03-31 14:46:22.465521",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Benefit Application",

View File

@ -21,7 +21,6 @@ frappe.ui.form.on('Employee Benefit Claim', {
callback: function(r) {
if (r.message) {
frm.set_value('currency', r.message);
frm.set_df_property('currency', 'hidden', 0);
}
}
});

View File

@ -125,10 +125,9 @@
"label": "Attachments"
},
{
"default": "Company:company:default_currency",
"depends_on": "eval: doc.employee",
"fieldname": "currency",
"fieldtype": "Link",
"hidden": 1,
"label": "Currency",
"options": "Currency",
"read_only": 1,
@ -145,7 +144,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-11-25 11:49:56.097352",
"modified": "2021-03-31 15:51:51.489269",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Benefit Claim",

View File

@ -75,7 +75,6 @@
"reqd": 1
},
{
"default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
"fieldname": "currency",
"fieldtype": "Link",
@ -95,7 +94,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-10-20 17:22:16.468042",
"modified": "2021-03-31 14:48:00.919839",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Incentive",

View File

@ -47,5 +47,26 @@ frappe.ui.form.on('Employee Tax Exemption Declaration', {
});
}).addClass("btn-primary");
}
},
employee: function(frm) {
if (frm.doc.employee) {
frm.trigger('get_employee_currency');
}
},
get_employee_currency: function(frm) {
frappe.call({
method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
args: {
employee: frm.doc.employee,
},
callback: function(r) {
if (r.message) {
frm.set_value('currency', r.message);
frm.refresh_fields();
}
}
});
}
});

View File

@ -108,7 +108,7 @@
"read_only": 1
},
{
"default": "Company:company:default_currency",
"depends_on": "eval: doc.employee",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
@ -119,7 +119,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-10-20 16:42:24.493761",
"modified": "2021-03-31 20:41:57.387749",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Tax Exemption Declaration",

View File

@ -58,5 +58,26 @@ frappe.ui.form.on('Employee Tax Exemption Proof Submission', {
currency: function(frm) {
frm.refresh_fields();
}
},
employee: function(frm) {
if (frm.doc.employee) {
frm.trigger('get_employee_currency');
}
},
get_employee_currency: function(frm) {
frappe.call({
method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
args: {
employee: frm.doc.employee,
},
callback: function(r) {
if (r.message) {
frm.set_value('currency', r.message);
frm.refresh_fields();
}
}
});
},
});

View File

@ -131,7 +131,7 @@
"read_only": 1
},
{
"default": "Company:company:default_currency",
"depends_on": "eval: doc.employee",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
@ -142,7 +142,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-10-20 16:47:03.410020",
"modified": "2021-03-31 20:48:32.639885",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Employee Tax Exemption Proof Submission",

View File

@ -93,7 +93,7 @@
"options": "Income Tax Slab Other Charges"
},
{
"default": "Company:company:default_currency",
"fetch_from": "company.default_currency",
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
@ -104,7 +104,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-10-19 13:54:24.728075",
"modified": "2021-03-31 20:53:33.323712",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Income Tax Slab",

View File

@ -93,7 +93,6 @@
"reqd": 1
},
{
"default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.employee)",
"fieldname": "currency",
"fieldtype": "Link",
@ -106,7 +105,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-10-20 17:27:47.003134",
"modified": "2021-03-31 14:50:29.401020",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Retention Bonus",

View File

@ -216,7 +216,7 @@ frappe.ui.form.on('Salary Slip Timesheet', {
});
var set_totals = function(frm) {
if (frm.doc.docstatus === 0) {
if (frm.doc.docstatus === 0 && frm.doc.doctype === "Salary Slip") {
if (frm.doc.earnings || frm.doc.deductions) {
frappe.call({
method: "set_totals",

View File

@ -500,7 +500,6 @@
"fieldtype": "Column Break"
},
{
"default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.salary_structure)",
"fetch_from": "salary_structure.currency",
"fieldname": "currency",
@ -632,7 +631,7 @@
"idx": 9,
"is_submittable": 1,
"links": [],
"modified": "2021-02-19 11:48:05.383945",
"modified": "2021-03-31 15:39:28.817166",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Slip",

View File

@ -232,7 +232,7 @@
"idx": 1,
"is_submittable": 1,
"links": [],
"modified": "2020-09-30 11:30:32.190798",
"modified": "2021-03-31 15:41:12.342380",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Structure",

View File

@ -125,7 +125,6 @@
"options": "Income Tax Slab"
},
{
"default": "Company:company:default_currency",
"depends_on": "eval:(doc.docstatus==1 || doc.salary_structure)",
"fetch_from": "salary_structure.currency",
"fieldname": "currency",
@ -146,7 +145,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-11-30 18:07:48.251311",
"modified": "2021-03-31 15:49:36.361253",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Structure Assignment",

View File

@ -18,8 +18,8 @@ frappe.ui.form.on("Project", {
};
},
onload: function (frm) {
var so = frappe.meta.get_docfield("Project", "sales_order");
so.get_route_options_for_new_doc = function (field) {
const so = frm.get_docfield("sales_order");
so.get_route_options_for_new_doc = () => {
if (frm.is_new()) return;
return {
"customer": frm.doc.customer,

View File

@ -1167,6 +1167,11 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
this.calculate_net_weight();
}
// for handling customization not to fetch price list rate
if(frappe.flags.dont_fetch_price_list_rate) {
return
}
if (!dont_fetch_price_list_rate &&
frappe.meta.has_field(doc.doctype, "price_list_currency")) {
this.apply_price_list(item, true);

View File

@ -919,7 +919,8 @@
"minLength": 1,
"maxLength": 15,
"pattern": "^([0-9A-Z/-]){1,15}$",
"description": "Tranport Document Number"
"description": "Tranport Document Number",
"validationMsg": "Transport Receipt No is invalid"
},
"TransDocDt": {
"type": "string",

View File

@ -334,8 +334,11 @@ def make_einvoice(invoice):
buyer_details = get_overseas_address_details(invoice.customer_address)
else:
buyer_details = get_party_details(invoice.customer_address)
place_of_supply = get_place_of_supply(invoice, invoice.doctype) or sanitize_for_json(invoice.billing_address_gstin)
place_of_supply = place_of_supply[:2]
place_of_supply = get_place_of_supply(invoice, invoice.doctype)
if place_of_supply:
place_of_supply = place_of_supply.split('-')[0]
else:
place_of_supply = sanitize_for_json(invoice.billing_address_gstin)[:2]
buyer_details.update(dict(place_of_supply=place_of_supply))
shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({})

View File

@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe, os, json
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.permissions import add_permission, update_permission_property
from erpnext.regional.india import states
from erpnext.accounts.utils import get_fiscal_year, FiscalYearError
@ -18,6 +19,7 @@ def setup(company=None, patch=True):
# TODO: for all countries
def setup_company_independent_fixtures():
make_custom_fields()
make_property_setters()
add_permissions()
add_custom_roles_for_reports()
frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test)
@ -110,6 +112,11 @@ def add_print_formats():
frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0)
frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0)
def make_property_setters():
# GST rules do not allow for an invoice no. bigger than 16 characters
make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '')
make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '')
def make_custom_fields(update=True):
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description',
@ -860,4 +867,4 @@ def create_gratuity_rule():
})
rule.flags.ignore_mandatory = True
rule.save()
rule.save()

View File

@ -230,13 +230,20 @@ class Customer(TransactionBase):
frappe.db.set(self, "customer_name", newdn)
def set_loyalty_program(self):
if self.loyalty_program: return
if self.loyalty_program:
return
loyalty_program = get_loyalty_programs(self)
if not loyalty_program: return
if not loyalty_program:
return
if len(loyalty_program) == 1:
self.loyalty_program = loyalty_program[0]
else:
frappe.msgprint(_("Multiple Loyalty Program found for the Customer. Please select manually."))
frappe.msgprint(
_("Multiple Loyalty Programs found for Customer {}. Please select manually.")
.format(frappe.bold(self.customer_name))
)
def create_onboarding_docs(self, args):
defaults = frappe.defaults.get_defaults()
@ -340,7 +347,6 @@ def _set_missing_values(source, target):
@frappe.whitelist()
def get_loyalty_programs(doc):
''' returns applicable loyalty programs for a customer '''
from frappe.desk.treeview import get_children
lp_details = []
loyalty_programs = frappe.get_all("Loyalty Program",
@ -349,15 +355,33 @@ def get_loyalty_programs(doc):
"ifnull(to_date, '2500-01-01')": [">=", today()]})
for loyalty_program in loyalty_programs:
customer_groups = [d.value for d in get_children("Customer Group", loyalty_program.customer_group)] + [loyalty_program.customer_group]
customer_territories = [d.value for d in get_children("Territory", loyalty_program.customer_territory)] + [loyalty_program.customer_territory]
if (not loyalty_program.customer_group or doc.customer_group in customer_groups)\
and (not loyalty_program.customer_territory or doc.territory in customer_territories):
if (
(not loyalty_program.customer_group
or doc.customer_group in get_nested_links(
"Customer Group",
loyalty_program.customer_group,
doc.flags.ignore_permissions
))
and (not loyalty_program.customer_territory
or doc.territory in get_nested_links(
"Territory",
loyalty_program.customer_territory,
doc.flags.ignore_permissions
))
):
lp_details.append(loyalty_program.name)
return lp_details
def get_nested_links(link_doctype, link_name, ignore_permissions=False):
from frappe.desk.treeview import _get_children
links = [link_name]
for d in _get_children(link_doctype, link_name, ignore_permissions):
links.append(d.value)
return links
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
@ -572,4 +596,4 @@ def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, fil
""", {
'customer': customer,
'txt': '%%%s%%' % txt
})
})

View File

@ -150,7 +150,7 @@ class SalesOrder(SellingController):
if enq:
frappe.db.sql("update `tabOpportunity` set status = %s where name=%s",(flag,enq[0][0]))
def update_prevdoc_status(self, flag):
def update_prevdoc_status(self, flag=None):
for quotation in list(set([d.prevdoc_docname for d in self.get("items")])):
if quotation:
doc = frappe.get_doc("Quotation", quotation)

View File

@ -341,6 +341,9 @@ class TestSalesOrder(unittest.TestCase):
prev_total = so.get("base_total")
prev_total_in_words = so.get("base_in_words")
# get reserved qty before update items
reserved_qty_for_second_item = get_reserved_qty("_Test Item 2")
first_item_of_so = so.get("items")[0]
trans_item = json.dumps([
{'item_code' : first_item_of_so.item_code, 'rate' : first_item_of_so.rate, \
@ -354,6 +357,10 @@ class TestSalesOrder(unittest.TestCase):
self.assertEqual(so.get("items")[-1].rate, 200)
self.assertEqual(so.get("items")[-1].qty, 7)
self.assertEqual(so.get("items")[-1].amount, 1400)
# reserved qty should increase after adding row
self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item + 7)
self.assertEqual(so.status, 'To Deliver and Bill')
updated_total = so.get("base_total")
@ -373,6 +380,9 @@ class TestSalesOrder(unittest.TestCase):
create_dn_against_so(so.name, 2)
make_sales_invoice(so.name)
# get reserved qty before update items
reserved_qty_for_second_item = get_reserved_qty("_Test Item 2")
# add an item so as to try removing items
trans_item = json.dumps([
{"item_code": '_Test Item', "qty": 5, "rate":1000, "docname": so.get("items")[0].name},
@ -382,6 +392,9 @@ class TestSalesOrder(unittest.TestCase):
so.reload()
self.assertEqual(len(so.get("items")), 2)
# reserved qty should increase after adding row
self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item + 2)
# check if delivered items can be removed
trans_item = json.dumps([{
"item_code": '_Test Item 2',
@ -402,6 +415,10 @@ class TestSalesOrder(unittest.TestCase):
so.reload()
self.assertEqual(len(so.get("items")), 1)
# reserved qty should decrease (back to initial) after deleting row
self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item)
self.assertEqual(so.status, 'To Deliver and Bill')
@ -503,12 +520,18 @@ class TestSalesOrder(unittest.TestCase):
so = make_sales_order(item_code = "_Test Item", warehouse=None)
# get reserved qty of packed item
existing_reserved_qty = get_reserved_qty("_Packed Item")
added_item = json.dumps([{"item_code" : "_Product Bundle Item", "rate" : 200, 'qty' : 2}])
update_child_qty_rate('Sales Order', added_item, so.name)
so.reload()
self.assertEqual(so.packed_items[0].qty, 4)
# reserved qty in packed item should increase after adding bundle item
self.assertEqual(get_reserved_qty("_Packed Item"), existing_reserved_qty + 4)
# test uom and conversion factor change
update_uom_conv_factor = json.dumps([{
'item_code': so.get("items")[0].item_code,
@ -523,6 +546,9 @@ class TestSalesOrder(unittest.TestCase):
so.reload()
self.assertEqual(so.packed_items[0].qty, 8)
# reserved qty in packed item should increase after changing bundle item uom
self.assertEqual(get_reserved_qty("_Packed Item"), existing_reserved_qty + 8)
def test_update_child_with_tax_template(self):
"""
Test Action: Create a SO with one item having its tax account head already in the SO.

View File

@ -1,4 +1,5 @@
{
"actions": [],
"autoname": "MAT-BIN-.YYYY.-.#####",
"creation": "2013-01-10 16:34:25",
"doctype": "DocType",
@ -112,7 +113,8 @@
{
"fieldname": "reserved_qty_for_sub_contract",
"fieldtype": "Float",
"label": "Reserved Qty for sub contract"
"label": "Reserved Qty for sub contract",
"read_only": 1
},
{
"fieldname": "ma_rate",
@ -166,7 +168,8 @@
"hide_toolbar": 1,
"idx": 1,
"in_create": 1,
"modified": "2019-11-18 18:34:59.456882",
"links": [],
"modified": "2021-03-30 23:09:39.572776",
"modified_by": "Administrator",
"module": "Stock",
"name": "Bin",
@ -196,5 +199,6 @@
],
"quick_entry": 1,
"search_fields": "item_code,warehouse",
"sort_field": "modified",
"sort_order": "ASC"
}

View File

@ -12,6 +12,7 @@
"item_name": "_Test Item",
"apply_warehouse_wise_reorder_level": 1,
"gst_hsn_code": "999800",
"opening_stock": 10,
"valuation_rate": 100,
"item_defaults": [{
"company": "_Test Company",

View File

@ -354,6 +354,10 @@ frappe.ui.form.on('Material Request', {
},
material_request_type: function(frm) {
frm.toggle_reqd('customer', frm.doc.material_request_type=="Customer Provided");
if (frm.doc.material_request_type !== 'Material Transfer' && frm.doc.set_from_warehouse) {
frm.set_value('set_from_warehouse', '');
}
},
});

View File

@ -20,9 +20,9 @@
"company",
"amended_from",
"warehouse_section",
"set_warehouse",
"column_break5",
"set_from_warehouse",
"column_break5",
"set_warehouse",
"items_section",
"scan_barcode",
"items",
@ -314,7 +314,7 @@
"idx": 70,
"is_submittable": 1,
"links": [],
"modified": "2020-09-19 01:04:09.285862",
"modified": "2021-03-31 23:52:55.392512",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request",

View File

@ -64,17 +64,21 @@ class QualityInspection(Document):
(quality_inspection, self.modified, self.reference_name, self.item_code))
else:
args = [quality_inspection, self.modified, self.reference_name, self.item_code]
doctype = self.reference_type + ' Item'
if self.reference_type == 'Stock Entry':
doctype = 'Stock Entry Detail'
if self.reference_type and self.reference_name:
conditions = ""
if self.batch_no and self.docstatus == 1:
conditions += " and t1.batch_no = '%s'"%(self.batch_no)
conditions += " and t1.batch_no = %s"
args.append(self.batch_no)
if self.docstatus == 2: # if cancel, then remove qi link wherever same name
conditions += " and t1.quality_inspection = '%s'"%(self.name)
conditions += " and t1.quality_inspection = %s"
args.append(self.name)
frappe.db.sql("""
UPDATE
@ -87,7 +91,7 @@ class QualityInspection(Document):
and t1.parent = t2.name
{conditions}
""".format(parent_doc=self.reference_type, child_doc=doctype, conditions=conditions),
(quality_inspection, self.modified, self.reference_name, self.item_code))
args)
def inspect_and_set_status(self):
for reading in self.readings: