From 5468a3b0b7418b0aec47b4f96cc17ca85e57bd6a Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 8 May 2024 16:23:46 +0200 Subject: [PATCH 01/17] fix(Sales Order): only show permitted actions (cherry picked from commit c29d95537185d909612103b65573242a91ef0d70) # Conflicts: # erpnext/selling/doctype/sales_order/sales_order.js --- .../doctype/sales_order/sales_order.js | 223 +++++++++++++++++- 1 file changed, 222 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 2995a1e99bc..29ef186c42a 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -47,6 +47,7 @@ frappe.ui.form.on("Sales Order", { frm.set_df_property('packed_items', 'cannot_add_rows', true); frm.set_df_property('packed_items', 'cannot_delete_rows', true); }, +<<<<<<< HEAD refresh: function(frm) { if(frm.doc.docstatus === 1 && frm.doc.status !== 'Closed' && flt(frm.doc.per_delivered, 6) < 100 && flt(frm.doc.per_billed, 6) < 100) { @@ -57,6 +58,64 @@ frappe.ui.form.on("Sales Order", { child_doctype: "Sales Order Detail", cannot_add_row: false, }) +======= + + refresh: function (frm) { + if (frm.doc.docstatus === 1) { + if ( + frm.doc.status !== "Closed" && + flt(frm.doc.per_delivered, 2) < 100 && + flt(frm.doc.per_billed, 2) < 100 && + frm.has_perm("write") + ) { + frm.add_custom_button(__("Update Items"), () => { + erpnext.utils.update_child_items({ + frm: frm, + child_docname: "items", + child_doctype: "Sales Order Detail", + cannot_add_row: false, + has_reserved_stock: frm.doc.__onload && frm.doc.__onload.has_reserved_stock, + }); + }); + + // Stock Reservation > Reserve button should only be visible if the SO has unreserved stock and no Pick List is created against the SO. + if ( + frm.doc.__onload && + frm.doc.__onload.has_unreserved_stock && + flt(frm.doc.per_picked) === 0 && + frappe.model.can_create("Stock Reservation Entry") + ) { + frm.add_custom_button( + __("Reserve"), + () => frm.events.create_stock_reservation_entries(frm), + __("Stock Reservation") + ); + } + } + + // Stock Reservation > Unreserve button will be only visible if the SO has un-delivered reserved stock. + if ( + frm.doc.__onload && + frm.doc.__onload.has_reserved_stock && + frappe.model.can_cancel("Stock Reservation Entry") + ) { + frm.add_custom_button( + __("Unreserve"), + () => frm.events.cancel_stock_reservation_entries(frm), + __("Stock Reservation") + ); + } + + frm.doc.items.forEach((item) => { + if (flt(item.stock_reserved_qty) > 0 && frappe.model.can_read("Stock Reservation Entry")) { + frm.add_custom_button( + __("Reserved Stock"), + () => frm.events.show_reserved_stock(frm), + __("Stock Reservation") + ); + return; + } +>>>>>>> c29d955371 (fix(Sales Order): only show permitted actions) }); } @@ -66,6 +125,7 @@ frappe.ui.form.on("Sales Order", { }, get_items_from_internal_purchase_order(frm) { +<<<<<<< HEAD frm.add_custom_button(__('Purchase Order'), () => { erpnext.utils.map_current_doc({ method: 'erpnext.buying.doctype.purchase_order.purchase_order.make_inter_company_sales_order', @@ -87,6 +147,37 @@ frappe.ui.form.on("Sales Order", { } }); }, __('Get Items From')); +======= + if (!frappe.model.can_read("Purchase Order")) { + return; + } + + frm.add_custom_button( + __("Purchase Order"), + () => { + erpnext.utils.map_current_doc({ + method: "erpnext.buying.doctype.purchase_order.purchase_order.make_inter_company_sales_order", + source_doctype: "Purchase Order", + target: frm, + setters: [ + { + label: "Supplier", + fieldname: "supplier", + fieldtype: "Link", + options: "Supplier", + }, + ], + get_query_filters: { + company: frm.doc.company, + is_internal_supplier: 1, + docstatus: 1, + status: ["!=", "Completed"], + }, + }); + }, + __("Get Items From") + ); +>>>>>>> c29d955371 (fix(Sales Order): only show permitted actions) }, // When multiple companies are set up. in case company name is changed set default company address @@ -223,8 +314,22 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex } } +<<<<<<< HEAD if (flt(doc.per_picked, 6) < 100 && flt(doc.per_delivered, 6) < 100) { this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create')); +======= + if ( + (!doc.__onload || !doc.__onload.has_reserved_stock) && + flt(doc.per_picked, 2) < 100 && + flt(doc.per_delivered, 2) < 100 && + frappe.model.can_create("Pick List") + ) { + this.frm.add_custom_button( + __("Pick List"), + () => this.create_pick_list(), + __("Create") + ); +>>>>>>> c29d955371 (fix(Sales Order): only show permitted actions) } const order_is_a_sale = ["Sales", "Shopping Cart"].indexOf(doc.order_type) !== -1; @@ -233,6 +338,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex const order_is_a_custom_sale = ["Sales", "Shopping Cart", "Maintenance"].indexOf(doc.order_type) === -1; // delivery note +<<<<<<< HEAD if(flt(doc.per_delivered, 6) < 100 && (order_is_a_sale || order_is_a_custom_sale) && allow_delivery) { this.frm.add_custom_button(__('Delivery Note'), () => this.make_delivery_note_based_on_delivery_date(), __('Create')); this.frm.add_custom_button(__('Work Order'), () => this.make_work_order(), __('Create')); @@ -269,9 +375,95 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex this.frm.add_custom_button(__('Subscription'), function() { erpnext.utils.make_subscription(doc.doctype, doc.name) }, __('Create')) +======= + if ( + flt(doc.per_delivered, 2) < 100 && + (order_is_a_sale || order_is_a_custom_sale) && + allow_delivery + ) { + if (frappe.model.can_create("Delivery Note")) { + this.frm.add_custom_button( + __("Delivery Note"), + () => this.make_delivery_note_based_on_delivery_date(true), + __("Create") + ); + } + + if (frappe.model.can_create("Work Order")) { + this.frm.add_custom_button( + __("Work Order"), + () => this.make_work_order(), + __("Create") + ); + } } - if (doc.docstatus === 1 && !doc.inter_company_order_reference) { + // sales invoice + if (flt(doc.per_billed, 2) < 100 && frappe.model.can_create("Sales Invoice")) { + this.frm.add_custom_button( + __("Sales Invoice"), + () => me.make_sales_invoice(), + __("Create") + ); + } + + // material request + if ( + (!doc.order_type || + ((order_is_a_sale || order_is_a_custom_sale) && + flt(doc.per_delivered, 2) < 100)) && + frappe.model.can_create("Material Request") + ) { + this.frm.add_custom_button( + __("Material Request"), + () => this.make_material_request(), + __("Create") + ); + this.frm.add_custom_button( + __("Request for Raw Materials"), + () => this.make_raw_material_request(), + __("Create") + ); + } + + // Make Purchase Order + if (!this.frm.doc.is_internal_customer && frappe.model.can_create("Purchase Order")) { + this.frm.add_custom_button( + __("Purchase Order"), + () => this.make_purchase_order(), + __("Create") + ); + } + + // maintenance + if (flt(doc.per_delivered, 2) < 100 && (order_is_maintenance || order_is_a_custom_sale)) { + if (frappe.model.can_create("Maintenance Visit")) { + this.frm.add_custom_button( + __("Maintenance Visit"), + () => this.make_maintenance_visit(), + __("Create") + ); + } + if (frappe.model.can_create("Maintenance Schedule")) { + this.frm.add_custom_button( + __("Maintenance Schedule"), + () => this.make_maintenance_schedule(), + __("Create") + ); + } + } + + // project + if (flt(doc.per_delivered, 2) < 100 && frappe.model.can_create("Project")) { + this.frm.add_custom_button(__("Project"), () => this.make_project(), __("Create")); +>>>>>>> c29d955371 (fix(Sales Order): only show permitted actions) + } + + if ( + doc.docstatus === 1 && + !doc.inter_company_order_reference && + frappe.model.can_create("Purchase Order") + ) { let me = this; let internal = me.frm.doc.is_internal_customer; if (internal) { @@ -285,17 +477,46 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex } } // payment request +<<<<<<< HEAD if(flt(doc.per_billed, precision('per_billed', doc)) < 100 + frappe.boot.sysdefaults.over_billing_allowance) { this.frm.add_custom_button(__('Payment Request'), () => this.make_payment_request(), __('Create')); this.frm.add_custom_button(__('Payment'), () => this.make_payment_entry(), __('Create')); +======= + if ( + flt(doc.per_billed, precision("per_billed", doc)) < + 100 + frappe.boot.sysdefaults.over_billing_allowance + ) { + if (frappe.model.can_create("Payment Request")) { + this.frm.add_custom_button( + __("Payment Request"), + () => this.make_payment_request(), + __("Create") + ); + } + + if (frappe.model.can_create("Payment Entry")) { + this.frm.add_custom_button( + __("Payment"), + () => this.make_payment_entry(), + __("Create") + ); + } +>>>>>>> c29d955371 (fix(Sales Order): only show permitted actions) } this.frm.page.set_inner_btn_group_as_primary(__('Create')); } } +<<<<<<< HEAD if (this.frm.doc.docstatus===0) { this.frm.add_custom_button(__('Quotation'), function() { +======= + if (this.frm.doc.docstatus === 0 && frappe.model.can_read("Quotation")) { + this.frm.add_custom_button( + __("Quotation"), + function () { +>>>>>>> c29d955371 (fix(Sales Order): only show permitted actions) let d = erpnext.utils.map_current_doc({ method: "erpnext.selling.doctype.quotation.quotation.make_sales_order", source_doctype: "Quotation", From 0aa246c39ef933028c0335abaa179b4071754c63 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Wed, 8 May 2024 17:55:16 +0200 Subject: [PATCH 02/17] fix(Delivery Note): only show permitted actions (cherry picked from commit 418bdc1dcc0c8c8eaaa6555b3689436515270c7c) # Conflicts: # erpnext/stock/doctype/delivery_note/delivery_note.js --- erpnext/public/js/controllers/transaction.js | 2 +- .../doctype/delivery_note/delivery_note.js | 141 +++++++++++++++++- 2 files changed, 141 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 66ac9582b2d..d6c5885891e 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -265,7 +265,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } const me = this; - if (!this.frm.is_new() && this.frm.doc.docstatus === 0) { + if (!this.frm.is_new() && this.frm.doc.docstatus === 0 && frappe.model.can_create("Quality Inspection")) { this.frm.add_custom_button(__("Quality Inspection(s)"), () => { me.make_quality_inspection(); }, __("Create")); diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 77545e0e1ad..12379670190 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -85,6 +85,7 @@ frappe.ui.form.on("Delivery Note", { erpnext.stock.delivery_note.set_print_hide(frm.doc); }, +<<<<<<< HEAD refresh: function(frm) { if (frm.doc.docstatus === 1 && frm.doc.is_return === 1 && frm.doc.per_billed !== 100) { frm.add_custom_button(__('Credit Note'), function() { @@ -94,9 +95,33 @@ frappe.ui.form.on("Delivery Note", { }) }, __('Create')); frm.page.set_inner_btn_group_as_primary(__('Create')); +======= + refresh: function (frm) { + if ( + frm.doc.docstatus === 1 && + frm.doc.is_return === 1 && + frm.doc.per_billed !== 100 && + frappe.model.can_create("Sales Invoice") + ) { + frm.add_custom_button( + __("Credit Note"), + function () { + frappe.model.open_mapped_doc({ + method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice", + frm: cur_frm, + }); + }, + __("Create") + ); + frm.page.set_inner_btn_group_as_primary(__("Create")); +>>>>>>> 418bdc1dcc (fix(Delivery Note): only show permitted actions) } - if (frm.doc.docstatus == 1 && !frm.doc.inter_company_reference) { + if ( + frm.doc.docstatus == 1 && + !frm.doc.inter_company_reference && + frappe.model.can_create("Purchase Receipt") + ) { let internal = frm.doc.is_internal_customer; if (internal) { let button_label = (frm.doc.company === frm.doc.represents_company) ? "Internal Purchase Receipt" : @@ -135,6 +160,7 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends erpn refresh(doc, dt, dn) { var me = this; super.refresh(); +<<<<<<< HEAD if ((!doc.is_return) && (doc.status!="Closed" || this.frm.is_new())) { if (this.frm.doc.docstatus===0) { this.frm.add_custom_button(__('Sales Order'), @@ -193,6 +219,109 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends erpn }) }, __('Create') ); } +======= + if ( + !doc.is_return && + (doc.status != "Closed" || this.frm.is_new()) && + this.frm.has_perm("write") && + frappe.model.can_read("Sales Order") && + this.frm.doc.docstatus === 0 + ) { + this.frm.add_custom_button( + __("Sales Order"), + function () { + if (!me.frm.doc.customer) { + frappe.throw({ + title: __("Mandatory"), + message: __("Please Select a Customer"), + }); + } + erpnext.utils.map_current_doc({ + method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note", + args: { + for_reserved_stock: 1, + }, + source_doctype: "Sales Order", + target: me.frm, + setters: { + customer: me.frm.doc.customer, + }, + get_query_filters: { + docstatus: 1, + status: ["not in", ["Closed", "On Hold"]], + per_delivered: ["<", 99.99], + company: me.frm.doc.company, + project: me.frm.doc.project || undefined, + }, + }); + }, + __("Get Items From") + ); + } + + if (!doc.is_return && doc.status != "Closed" && frappe.model.can_create("Shipment")) { + if (doc.docstatus == 1) { + this.frm.add_custom_button( + __("Shipment"), + function () { + me.make_shipment(); + }, + __("Create") + ); + } + + if ( + flt(doc.per_installed, 2) < 100 && + doc.docstatus == 1 && + frappe.model.can_create("Installation Note") + ) { + this.frm.add_custom_button( + __("Installation Note"), + function () { + me.make_installation_note(); + }, + __("Create") + ); + } + + if (doc.docstatus == 1 && this.frm.has_perm("create")) { + this.frm.add_custom_button( + __("Sales Return"), + function () { + me.make_sales_return(); + }, + __("Create") + ); + } + + if (doc.docstatus == 1 && frappe.model.can_create("Delivery Trip")) { + this.frm.add_custom_button( + __("Delivery Trip"), + function () { + me.make_delivery_trip(); + }, + __("Create") + ); + } + + if ( + doc.docstatus == 0 && + !doc.__islocal && + doc.__onload && + doc.__onload.has_unpacked_items && + frappe.model.can_create("Packing Slip") + ) { + this.frm.add_custom_button( + __("Packing Slip"), + function () { + frappe.model.open_mapped_doc({ + method: "erpnext.stock.doctype.delivery_note.delivery_note.make_packing_slip", + frm: me.frm, + }); + }, + __("Create") + ); +>>>>>>> 418bdc1dcc (fix(Delivery Note): only show permitted actions) } if (!doc.__islocal && doc.docstatus==1) { @@ -211,7 +340,17 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends erpn } } +<<<<<<< HEAD if(doc.docstatus==1 && !doc.is_return && doc.status!="Closed" && flt(doc.per_billed) < 100) { +======= + if ( + doc.docstatus == 1 && + !doc.is_return && + doc.status != "Closed" && + flt(doc.per_billed) < 100 && + frappe.model.can_create("Sales Invoice") + ) { +>>>>>>> 418bdc1dcc (fix(Delivery Note): only show permitted actions) // show Make Invoice button only if Delivery Note is not created from Sales Invoice var from_sales_invoice = false; from_sales_invoice = me.frm.doc.items.some(function(item) { From 966521260752b50ddc5586c8dbd7a8a8f4012e5a Mon Sep 17 00:00:00 2001 From: ljain112 Date: Thu, 20 Jun 2024 11:11:43 +0530 Subject: [PATCH 03/17] fix: consistent query field name in item wise purchase register with item wise sales register --- .../item_wise_purchase_register/item_wise_purchase_register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 80c246cad55..06afb4c0e8f 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -322,7 +322,7 @@ def get_items(filters, additional_table_columns): .left_join(Item) .on(pii.item_code == Item.name) .select( - pii.name.as_("pii_name"), + pii.name, pii.parent, pi.posting_date, pi.credit_to, From 0e6edf763e9210d7bd57190e28d7c9fe77bafee1 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:15:26 +0200 Subject: [PATCH 04/17] chore: resolve conflicts --- erpnext/public/js/controllers/transaction.js | 6 +- .../doctype/sales_order/sales_order.js | 244 ++++-------------- .../doctype/delivery_note/delivery_note.js | 196 ++++---------- 3 files changed, 107 insertions(+), 339 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 7291a82719c..e3dae3e6f97 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -265,7 +265,11 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } const me = this; - if (!this.frm.is_new() && this.frm.doc.docstatus === 0 && frappe.model.can_create("Quality Inspection")) { + if ( + !this.frm.is_new() + && this.frm.doc.docstatus === 0 + && frappe.model.can_create("Quality Inspection") + ) { this.frm.add_custom_button(__("Quality Inspection(s)"), () => { me.make_quality_inspection(); }, __("Create")); diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index bfd5d6aadee..26113554a19 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -47,10 +47,14 @@ frappe.ui.form.on("Sales Order", { frm.set_df_property('packed_items', 'cannot_add_rows', true); frm.set_df_property('packed_items', 'cannot_delete_rows', true); }, -<<<<<<< HEAD refresh: function(frm) { - if(frm.doc.docstatus === 1 && frm.doc.status !== 'Closed' - && flt(frm.doc.per_delivered, 6) < 100 && flt(frm.doc.per_billed, 6) < 100) { + if( + frm.doc.docstatus === 1 + && frm.doc.status !== "Closed" + && flt(frm.doc.per_delivered, 6) < 100 + && flt(frm.doc.per_billed, 6) < 100 + && frm.has_perm("write") + ) { frm.add_custom_button(__('Update Items'), () => { erpnext.utils.update_child_items({ frm: frm, @@ -58,64 +62,6 @@ frappe.ui.form.on("Sales Order", { child_doctype: "Sales Order Detail", cannot_add_row: false, }) -======= - - refresh: function (frm) { - if (frm.doc.docstatus === 1) { - if ( - frm.doc.status !== "Closed" && - flt(frm.doc.per_delivered, 2) < 100 && - flt(frm.doc.per_billed, 2) < 100 && - frm.has_perm("write") - ) { - frm.add_custom_button(__("Update Items"), () => { - erpnext.utils.update_child_items({ - frm: frm, - child_docname: "items", - child_doctype: "Sales Order Detail", - cannot_add_row: false, - has_reserved_stock: frm.doc.__onload && frm.doc.__onload.has_reserved_stock, - }); - }); - - // Stock Reservation > Reserve button should only be visible if the SO has unreserved stock and no Pick List is created against the SO. - if ( - frm.doc.__onload && - frm.doc.__onload.has_unreserved_stock && - flt(frm.doc.per_picked) === 0 && - frappe.model.can_create("Stock Reservation Entry") - ) { - frm.add_custom_button( - __("Reserve"), - () => frm.events.create_stock_reservation_entries(frm), - __("Stock Reservation") - ); - } - } - - // Stock Reservation > Unreserve button will be only visible if the SO has un-delivered reserved stock. - if ( - frm.doc.__onload && - frm.doc.__onload.has_reserved_stock && - frappe.model.can_cancel("Stock Reservation Entry") - ) { - frm.add_custom_button( - __("Unreserve"), - () => frm.events.cancel_stock_reservation_entries(frm), - __("Stock Reservation") - ); - } - - frm.doc.items.forEach((item) => { - if (flt(item.stock_reserved_qty) > 0 && frappe.model.can_read("Stock Reservation Entry")) { - frm.add_custom_button( - __("Reserved Stock"), - () => frm.events.show_reserved_stock(frm), - __("Stock Reservation") - ); - return; - } ->>>>>>> c29d955371 (fix(Sales Order): only show permitted actions) }); } @@ -125,7 +71,10 @@ frappe.ui.form.on("Sales Order", { }, get_items_from_internal_purchase_order(frm) { -<<<<<<< HEAD + if (!frappe.model.can_read("Purchase Order")) { + return; + } + frm.add_custom_button(__('Purchase Order'), () => { erpnext.utils.map_current_doc({ method: 'erpnext.buying.doctype.purchase_order.purchase_order.make_inter_company_sales_order', @@ -147,37 +96,6 @@ frappe.ui.form.on("Sales Order", { } }); }, __('Get Items From')); -======= - if (!frappe.model.can_read("Purchase Order")) { - return; - } - - frm.add_custom_button( - __("Purchase Order"), - () => { - erpnext.utils.map_current_doc({ - method: "erpnext.buying.doctype.purchase_order.purchase_order.make_inter_company_sales_order", - source_doctype: "Purchase Order", - target: frm, - setters: [ - { - label: "Supplier", - fieldname: "supplier", - fieldtype: "Link", - options: "Supplier", - }, - ], - get_query_filters: { - company: frm.doc.company, - is_internal_supplier: 1, - docstatus: 1, - status: ["!=", "Completed"], - }, - }); - }, - __("Get Items From") - ); ->>>>>>> c29d955371 (fix(Sales Order): only show permitted actions) }, // When multiple companies are set up. in case company name is changed set default company address @@ -318,22 +236,12 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex } } -<<<<<<< HEAD - if (flt(doc.per_picked, 6) < 100 && flt(doc.per_delivered, 6) < 100) { - this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create')); -======= if ( - (!doc.__onload || !doc.__onload.has_reserved_stock) && - flt(doc.per_picked, 2) < 100 && - flt(doc.per_delivered, 2) < 100 && - frappe.model.can_create("Pick List") + flt(doc.per_picked, 6) < 100 + && flt(doc.per_delivered, 6) < 100 + && frappe.model.can_create("Pick List") ) { - this.frm.add_custom_button( - __("Pick List"), - () => this.create_pick_list(), - __("Create") - ); ->>>>>>> c29d955371 (fix(Sales Order): only show permitted actions) + this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create')); } const order_is_a_sale = ["Sales", "Shopping Cart"].indexOf(doc.order_type) !== -1; @@ -342,57 +250,18 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex const order_is_a_custom_sale = ["Sales", "Shopping Cart", "Maintenance"].indexOf(doc.order_type) === -1; // delivery note -<<<<<<< HEAD - if(flt(doc.per_delivered, 6) < 100 && (order_is_a_sale || order_is_a_custom_sale) && allow_delivery) { - this.frm.add_custom_button(__('Delivery Note'), () => this.make_delivery_note_based_on_delivery_date(), __('Create')); - this.frm.add_custom_button(__('Work Order'), () => this.make_work_order(), __('Create')); - } - - // sales invoice - if(flt(doc.per_billed, 6) < 100) { - this.frm.add_custom_button(__('Sales Invoice'), () => me.make_sales_invoice(), __('Create')); - } - - // material request - if(!doc.order_type || (order_is_a_sale || order_is_a_custom_sale) && flt(doc.per_delivered, 6) < 100) { - this.frm.add_custom_button(__('Material Request'), () => this.make_material_request(), __('Create')); - this.frm.add_custom_button(__('Request for Raw Materials'), () => this.make_raw_material_request(), __('Create')); - } - - // Make Purchase Order - if (!this.frm.doc.is_internal_customer) { - this.frm.add_custom_button(__('Purchase Order'), () => this.make_purchase_order(), __('Create')); - } - - // maintenance - if(flt(doc.per_delivered, 2) < 100 && (order_is_maintenance || order_is_a_custom_sale)) { - this.frm.add_custom_button(__('Maintenance Visit'), () => this.make_maintenance_visit(), __('Create')); - this.frm.add_custom_button(__('Maintenance Schedule'), () => this.make_maintenance_schedule(), __('Create')); - } - - // project - if(flt(doc.per_delivered, 2) < 100) { - this.frm.add_custom_button(__('Project'), () => this.make_project(), __('Create')); - } - - if(!doc.auto_repeat) { - this.frm.add_custom_button(__('Subscription'), function() { - erpnext.utils.make_subscription(doc.doctype, doc.name) - }, __('Create')) -======= - if ( - flt(doc.per_delivered, 2) < 100 && - (order_is_a_sale || order_is_a_custom_sale) && - allow_delivery + if( + flt(doc.per_delivered, 6) < 100 + && (order_is_a_sale || order_is_a_custom_sale) + && allow_delivery ) { if (frappe.model.can_create("Delivery Note")) { this.frm.add_custom_button( __("Delivery Note"), - () => this.make_delivery_note_based_on_delivery_date(true), + () => this.make_delivery_note_based_on_delivery_date(), __("Create") ); } - if (frappe.model.can_create("Work Order")) { this.frm.add_custom_button( __("Work Order"), @@ -403,7 +272,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex } // sales invoice - if (flt(doc.per_billed, 2) < 100 && frappe.model.can_create("Sales Invoice")) { + if(flt(doc.per_billed, 6) < 100 && frappe.model.can_create("Sales Invoice")) { this.frm.add_custom_button( __("Sales Invoice"), () => me.make_sales_invoice(), @@ -413,54 +282,66 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex // material request if ( - (!doc.order_type || - ((order_is_a_sale || order_is_a_custom_sale) && - flt(doc.per_delivered, 2) < 100)) && - frappe.model.can_create("Material Request") + ( + !doc.order_type + || (order_is_a_sale || order_is_a_custom_sale) + && flt(doc.per_delivered, 6) < 100 + ) + && frappe.model.can_create("Material Request") ) { this.frm.add_custom_button( - __("Material Request"), + __('Material Request'), () => this.make_material_request(), - __("Create") + __('Create') ); this.frm.add_custom_button( - __("Request for Raw Materials"), + __('Request for Raw Materials'), () => this.make_raw_material_request(), - __("Create") + __('Create') ); } // Make Purchase Order if (!this.frm.doc.is_internal_customer && frappe.model.can_create("Purchase Order")) { this.frm.add_custom_button( - __("Purchase Order"), + __('Purchase Order'), () => this.make_purchase_order(), - __("Create") + __('Create') ); } // maintenance - if (flt(doc.per_delivered, 2) < 100 && (order_is_maintenance || order_is_a_custom_sale)) { - if (frappe.model.can_create("Maintenance Visit")) { + if(flt(doc.per_delivered, 2) < 100 && (order_is_maintenance || order_is_a_custom_sale)) { + if(frappe.model.can_create("Maintenance Visit")) { this.frm.add_custom_button( - __("Maintenance Visit"), + __('Maintenance Visit'), () => this.make_maintenance_visit(), - __("Create") + __('Create') ); } - if (frappe.model.can_create("Maintenance Schedule")) { + + if(frappe.model.can_create("Maintenance Schedule")) { this.frm.add_custom_button( - __("Maintenance Schedule"), + __('Maintenance Schedule'), () => this.make_maintenance_schedule(), - __("Create") + __('Create') ); } } // project - if (flt(doc.per_delivered, 2) < 100 && frappe.model.can_create("Project")) { - this.frm.add_custom_button(__("Project"), () => this.make_project(), __("Create")); ->>>>>>> c29d955371 (fix(Sales Order): only show permitted actions) + if(flt(doc.per_delivered, 2) < 100 && frappe.model.can_create("Project")) { + this.frm.add_custom_button(__('Project'), () => this.make_project(), __('Create')); + } + + if(!doc.auto_repeat && frappe.model.can_create("Auto Repeat")) { + this.frm.add_custom_button( + __('Subscription'), + function() { + erpnext.utils.make_subscription(doc.doctype, doc.name) + }, + __('Create') + ); } if ( @@ -481,15 +362,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex } } // payment request -<<<<<<< HEAD if(flt(doc.per_billed, precision('per_billed', doc)) < 100 + frappe.boot.sysdefaults.over_billing_allowance) { - this.frm.add_custom_button(__('Payment Request'), () => this.make_payment_request(), __('Create')); - this.frm.add_custom_button(__('Payment'), () => this.make_payment_entry(), __('Create')); -======= - if ( - flt(doc.per_billed, precision("per_billed", doc)) < - 100 + frappe.boot.sysdefaults.over_billing_allowance - ) { if (frappe.model.can_create("Payment Request")) { this.frm.add_custom_button( __("Payment Request"), @@ -497,7 +370,6 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex __("Create") ); } - if (frappe.model.can_create("Payment Entry")) { this.frm.add_custom_button( __("Payment"), @@ -505,22 +377,14 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex __("Create") ); } ->>>>>>> c29d955371 (fix(Sales Order): only show permitted actions) } this.frm.page.set_inner_btn_group_as_primary(__('Create')); } } -<<<<<<< HEAD - if (this.frm.doc.docstatus===0) { + if (this.frm.doc.docstatus===0 && frappe.model.can_read("Quotation")) { this.frm.add_custom_button(__('Quotation'), function() { -======= - if (this.frm.doc.docstatus === 0 && frappe.model.can_read("Quotation")) { - this.frm.add_custom_button( - __("Quotation"), - function () { ->>>>>>> c29d955371 (fix(Sales Order): only show permitted actions) let d = erpnext.utils.map_current_doc({ method: "erpnext.selling.doctype.quotation.quotation.make_sales_order", source_doctype: "Quotation", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 12379670190..22f9728cce0 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -85,9 +85,8 @@ frappe.ui.form.on("Delivery Note", { erpnext.stock.delivery_note.set_print_hide(frm.doc); }, -<<<<<<< HEAD refresh: function(frm) { - if (frm.doc.docstatus === 1 && frm.doc.is_return === 1 && frm.doc.per_billed !== 100) { + if (frm.doc.docstatus === 1 && frm.doc.is_return === 1 && frm.doc.per_billed !== 100 && frappe.model.can_create("Sales Invoice")) { frm.add_custom_button(__('Credit Note'), function() { frappe.model.open_mapped_doc({ method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice", @@ -95,26 +94,6 @@ frappe.ui.form.on("Delivery Note", { }) }, __('Create')); frm.page.set_inner_btn_group_as_primary(__('Create')); -======= - refresh: function (frm) { - if ( - frm.doc.docstatus === 1 && - frm.doc.is_return === 1 && - frm.doc.per_billed !== 100 && - frappe.model.can_create("Sales Invoice") - ) { - frm.add_custom_button( - __("Credit Note"), - function () { - frappe.model.open_mapped_doc({ - method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice", - frm: cur_frm, - }); - }, - __("Create") - ); - frm.page.set_inner_btn_group_as_primary(__("Create")); ->>>>>>> 418bdc1dcc (fix(Delivery Note): only show permitted actions) } if ( @@ -160,87 +139,24 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends erpn refresh(doc, dt, dn) { var me = this; super.refresh(); -<<<<<<< HEAD - if ((!doc.is_return) && (doc.status!="Closed" || this.frm.is_new())) { - if (this.frm.doc.docstatus===0) { - this.frm.add_custom_button(__('Sales Order'), - function() { - if (!me.frm.doc.customer) { - frappe.throw({ - title: __("Mandatory"), - message: __("Please Select a Customer") - }); - } - erpnext.utils.map_current_doc({ - method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note", - source_doctype: "Sales Order", - target: me.frm, - setters: { - customer: me.frm.doc.customer, - }, - get_query_filters: { - docstatus: 1, - status: ["not in", ["Closed", "On Hold"]], - per_delivered: ["<", 99.99], - company: me.frm.doc.company, - project: me.frm.doc.project || undefined, - } - }) - }, __("Get Items From")); - } - } - - if (!doc.is_return && doc.status!="Closed") { - if(doc.docstatus == 1) { - this.frm.add_custom_button(__('Shipment'), function() { - me.make_shipment() }, __('Create')); - } - - if(flt(doc.per_installed, 2) < 100 && doc.docstatus==1) - this.frm.add_custom_button(__('Installation Note'), function() { - me.make_installation_note() }, __('Create')); - - if (doc.docstatus==1) { - this.frm.add_custom_button(__('Sales Return'), function() { - me.make_sales_return() }, __('Create')); - } - - if (doc.docstatus==1) { - this.frm.add_custom_button(__('Delivery Trip'), function() { - me.make_delivery_trip() }, __('Create')); - } - - if(doc.docstatus==0 && !doc.__islocal) { - if (doc.__onload && doc.__onload.has_unpacked_items) { - this.frm.add_custom_button(__('Packing Slip'), function() { - frappe.model.open_mapped_doc({ - method: "erpnext.stock.doctype.delivery_note.delivery_note.make_packing_slip", - frm: me.frm - }) }, __('Create') - ); - } -======= if ( - !doc.is_return && - (doc.status != "Closed" || this.frm.is_new()) && - this.frm.has_perm("write") && - frappe.model.can_read("Sales Order") && - this.frm.doc.docstatus === 0 + !doc.is_return + && (doc.status!="Closed" || this.frm.is_new()) + && this.frm.has_perm("write") + && frappe.model.can_read("Sales Order") + && this.frm.doc.docstatus===0 ) { this.frm.add_custom_button( - __("Sales Order"), - function () { + __('Sales Order'), + function() { if (!me.frm.doc.customer) { frappe.throw({ title: __("Mandatory"), - message: __("Please Select a Customer"), + message: __("Please Select a Customer") }); } erpnext.utils.map_current_doc({ method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note", - args: { - for_reserved_stock: 1, - }, source_doctype: "Sales Order", target: me.frm, setters: { @@ -252,76 +168,59 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends erpn per_delivered: ["<", 99.99], company: me.frm.doc.company, project: me.frm.doc.project || undefined, - }, - }); + } + }) }, __("Get Items From") ); } - if (!doc.is_return && doc.status != "Closed" && frappe.model.can_create("Shipment")) { - if (doc.docstatus == 1) { - this.frm.add_custom_button( - __("Shipment"), - function () { - me.make_shipment(); - }, - __("Create") - ); + if (!doc.is_return && doc.status!="Closed") { + if (doc.docstatus == 1 && frappe.model.can_create("Shipment")) { + this.frm.add_custom_button(__('Shipment'), function() { + me.make_shipment() }, __('Create')); } if ( - flt(doc.per_installed, 2) < 100 && - doc.docstatus == 1 && - frappe.model.can_create("Installation Note") + flt(doc.per_installed, 2) < 100 + && doc.docstatus==1 + && frappe.model.can_create("Installation Note") ) { this.frm.add_custom_button( - __("Installation Note"), - function () { - me.make_installation_note(); + __('Installation Note'), + function() { + me.make_installation_note() }, - __("Create") + __('Create') ); } - if (doc.docstatus == 1 && this.frm.has_perm("create")) { - this.frm.add_custom_button( - __("Sales Return"), - function () { - me.make_sales_return(); - }, - __("Create") - ); + if (doc.docstatus==1 && this.frm.has_perm("create")) { + this.frm.add_custom_button(__('Sales Return'), function() { + me.make_sales_return() }, __('Create')); } - if (doc.docstatus == 1 && frappe.model.can_create("Delivery Trip")) { - this.frm.add_custom_button( - __("Delivery Trip"), - function () { - me.make_delivery_trip(); - }, - __("Create") - ); + if (doc.docstatus==1 && frappe.model.can_create("Delivery Trip")) { + this.frm.add_custom_button(__('Delivery Trip'), function() { + me.make_delivery_trip() }, __('Create')); } if ( - doc.docstatus == 0 && - !doc.__islocal && - doc.__onload && - doc.__onload.has_unpacked_items && - frappe.model.can_create("Packing Slip") + doc.docstatus==0 + && !doc.__islocal + && frappe.model.can_create("Packing Slip") + && (doc.__onload && doc.__onload.has_unpacked_items) ) { this.frm.add_custom_button( - __("Packing Slip"), - function () { + __('Packing Slip'), + function() { frappe.model.open_mapped_doc({ method: "erpnext.stock.doctype.delivery_note.delivery_note.make_packing_slip", - frm: me.frm, + frm: me.frm }); }, - __("Create") + __('Create') ); ->>>>>>> 418bdc1dcc (fix(Delivery Note): only show permitted actions) } if (!doc.__islocal && doc.docstatus==1) { @@ -340,17 +239,13 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends erpn } } -<<<<<<< HEAD - if(doc.docstatus==1 && !doc.is_return && doc.status!="Closed" && flt(doc.per_billed) < 100) { -======= - if ( - doc.docstatus == 1 && - !doc.is_return && - doc.status != "Closed" && - flt(doc.per_billed) < 100 && - frappe.model.can_create("Sales Invoice") + if( + doc.docstatus==1 + && !doc.is_return + && doc.status!="Closed" + && flt(doc.per_billed) < 100 + && frappe.model.can_create("Sales Invoice") ) { ->>>>>>> 418bdc1dcc (fix(Delivery Note): only show permitted actions) // show Make Invoice button only if Delivery Note is not created from Sales Invoice var from_sales_invoice = false; from_sales_invoice = me.frm.doc.items.some(function(item) { @@ -369,7 +264,12 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends erpn } erpnext.stock.delivery_note.set_print_hide(doc, dt, dn); - if(doc.docstatus==1 && !doc.is_return && !doc.auto_repeat) { + if( + doc.docstatus==1 + && !doc.is_return + && !doc.auto_repeat + && frappe.model.can_create("Auto Repeat") + ) { cur_frm.add_custom_button(__('Subscription'), function() { erpnext.utils.make_subscription(doc.doctype, doc.name) }, __('Create')) From 22216b275c810ba79bfca6fe5d4f68a58384bf11 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:21:30 +0200 Subject: [PATCH 05/17] refactor: remove use of can_create for Payment Request (#41647) (cherry picked from commit 47bc5691a1579b88e0a430f6fbe5ff6309486ccf) --- erpnext/selling/doctype/sales_order/sales_order.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 26113554a19..1a6658f5418 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -363,13 +363,12 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex } // payment request if(flt(doc.per_billed, precision('per_billed', doc)) < 100 + frappe.boot.sysdefaults.over_billing_allowance) { - if (frappe.model.can_create("Payment Request")) { - this.frm.add_custom_button( - __("Payment Request"), - () => this.make_payment_request(), - __("Create") - ); - } + this.frm.add_custom_button( + __("Payment Request"), + () => this.make_payment_request(), + __("Create") + ); + if (frappe.model.can_create("Payment Entry")) { this.frm.add_custom_button( __("Payment"), From 6028a19e6ba613979947c1c3d8318d0bc38d2b3f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 21 Jun 2024 17:21:32 +0530 Subject: [PATCH 06/17] fix: incorrect against_account upon reposting (cherry picked from commit 20c4098399d5cb276d373074036a738e6cee67b0) --- .../accounts/doctype/purchase_invoice/purchase_invoice.py | 6 +++++- .../repost_accounting_ledger/repost_accounting_ledger.py | 4 ++++ erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index e0debba6fd5..2015dd1b634 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -413,7 +413,7 @@ class PurchaseInvoice(BuyingController): for item in self.get("items"): validate_account_head(item.idx, item.expense_account, self.company, "Expense") - def set_against_expense_account(self): + def set_against_expense_account(self, force=False): against_accounts = [] for item in self.get("items"): if item.expense_account and (item.expense_account not in against_accounts): @@ -421,6 +421,10 @@ class PurchaseInvoice(BuyingController): self.against_expense_account = ",".join(against_accounts) + def force_set_against_expense_account(self): + self.set_against_expense_account() + frappe.db.set_value(self.doctype, self.name, "against_expense_account", self.against_expense_account) + def po_required(self): if frappe.db.get_value("Buying Settings", None, "po_required") == "Yes": if frappe.get_value( diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py index 15478ab8633..550e80ec8bc 100644 --- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py +++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py @@ -149,6 +149,10 @@ def start_repost(account_repost_doc=str) -> None: doc.make_gl_entries_on_cancel() doc.docstatus = 1 + if doc.doctype == "Sales Invoice": + doc.force_set_against_income_account() + else: + doc.force_set_against_expense_account() doc.make_gl_entries() elif doc.doctype in ["Payment Entry", "Journal Entry", "Expense Claim"]: diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 3ea4b91641c..009c48a385c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -768,6 +768,10 @@ class SalesInvoice(SellingController): against_acc.append(d.income_account) self.against_income_account = ",".join(against_acc) + def force_set_against_income_account(self): + self.set_against_income_account() + frappe.db.set_value(self.doctype, self.name, "against_income_account", self.against_income_account) + def add_remarks(self): if not self.remarks: if self.po_no and self.po_date: From a2ee4631cc097b666815cb8e6a9eb9fd17df153e Mon Sep 17 00:00:00 2001 From: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Date: Tue, 25 Jun 2024 16:04:20 +0530 Subject: [PATCH 07/17] fix: asset value correction in fixed asset register (#41943) --- .../report/fixed_asset_register/fixed_asset_register.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index 4ff04c80434..61e0828aae4 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -125,9 +125,10 @@ def get_data(filters): if assets_linked_to_fb and asset.calculate_depreciation and asset.asset_id not in assets_linked_to_fb: continue - asset_value = get_asset_value_after_depreciation( - asset.asset_id, finance_book - ) or get_asset_value_after_depreciation(asset.asset_id) + depreciation_amount = depreciation_amount_map.get(asset.asset_id) or 0.0 + asset_value = ( + asset.gross_purchase_amount - asset.opening_accumulated_depreciation - depreciation_amount + ) row = { "asset_id": asset.asset_id, @@ -139,7 +140,7 @@ def get_data(filters): or pi_supplier_map.get(asset.purchase_invoice), "gross_purchase_amount": asset.gross_purchase_amount, "opening_accumulated_depreciation": asset.opening_accumulated_depreciation, - "depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0, + "depreciated_amount": depreciation_amount, "available_for_use_date": asset.available_for_use_date, "location": asset.location, "asset_category": asset.asset_category, From 21bf7fd1f8c9d4c1cd9f3b419089782e82658165 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 25 Jun 2024 17:38:03 +0530 Subject: [PATCH 08/17] fix: timeout while cancelling LCV (#42030) fix: timeout while canelling LCV --- erpnext/stock/stock_ledger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 0cd2ecdaaf5..71f4ca00707 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1529,6 +1529,7 @@ def get_next_stock_reco(kwargs): sle.batch_no, sle.actual_qty, ) + .force_index("item_warehouse") .where( (sle.item_code == kwargs.get("item_code")) & (sle.warehouse == kwargs.get("warehouse")) From 6de7a8de10468db4f2e27c3e81b559a83eba64a3 Mon Sep 17 00:00:00 2001 From: ljain112 Date: Fri, 21 Jun 2024 16:08:04 +0530 Subject: [PATCH 09/17] fix: dynamic_link for party in customer and supplier dashboard (cherry picked from commit c0a88008323bdf15e8752bddb4d7e27c602eeb7c) --- erpnext/buying/doctype/supplier/supplier_dashboard.py | 1 + erpnext/selling/doctype/customer/customer_dashboard.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/supplier/supplier_dashboard.py b/erpnext/buying/doctype/supplier/supplier_dashboard.py index 3bd306e6591..57bde90775d 100644 --- a/erpnext/buying/doctype/supplier/supplier_dashboard.py +++ b/erpnext/buying/doctype/supplier/supplier_dashboard.py @@ -9,6 +9,7 @@ def get_data(): ), "fieldname": "supplier", "non_standard_fieldnames": {"Payment Entry": "party", "Bank Account": "party"}, + "dynamic_links": {"party": ["Supplier", "party_type"]}, "transactions": [ {"label": _("Procurement"), "items": ["Request for Quotation", "Supplier Quotation"]}, {"label": _("Orders"), "items": ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]}, diff --git a/erpnext/selling/doctype/customer/customer_dashboard.py b/erpnext/selling/doctype/customer/customer_dashboard.py index 1b2296381e8..ec2f2301255 100644 --- a/erpnext/selling/doctype/customer/customer_dashboard.py +++ b/erpnext/selling/doctype/customer/customer_dashboard.py @@ -15,7 +15,10 @@ def get_data(): "Bank Account": "party", "Subscription": "party", }, - "dynamic_links": {"party_name": ["Customer", "quotation_to"]}, + "dynamic_links": { + "party_name": ["Customer", "quotation_to"], + "party": ["Customer", "party_type"], + }, "transactions": [ {"label": _("Pre Sales"), "items": ["Opportunity", "Quotation"]}, {"label": _("Orders"), "items": ["Sales Order", "Delivery Note", "Sales Invoice"]}, From 952a7b46d545318ec0a69c1b1c74185b07d4ad17 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 1 Jul 2024 15:49:48 +0530 Subject: [PATCH 10/17] fix: Re-open allows SO's to be over credit limit (cherry picked from commit 5eed78126374843d90328198cf5919dad8358018) --- erpnext/selling/doctype/sales_order/sales_order.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 5b4597125fb..f201228c2b1 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -343,6 +343,10 @@ class SalesOrder(SellingController): def update_status(self, status): self.check_modified_date() self.set_status(update=True, status=status) + # Upon Sales Order Re-open, check for credit limit. + # Limit should be checked after the 'Hold/Closed' status is reset. + if status == "Draft" and self.docstatus == 1: + self.check_credit_limit() self.update_reserved_qty() self.notify_update() clear_doctype_notifications(self) From f4fc26b52e6e544b538dd177852ac633a5446ef1 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 1 Jul 2024 16:43:16 +0530 Subject: [PATCH 11/17] test: credit check on Sales Order re-open (cherry picked from commit 60694e09c4feefefb98f52e542a9193c9ec2f0d0) # Conflicts: # erpnext/selling/doctype/sales_order/test_sales_order.py --- .../doctype/sales_order/test_sales_order.py | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 4ebd2a0c2d0..8783f6accda 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -9,7 +9,12 @@ from frappe.core.doctype.user_permission.test_user_permission import create_user from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, flt, getdate, nowdate, today +<<<<<<< HEAD from erpnext.controllers.accounts_controller import update_child_qty_rate +======= +from erpnext.accounts.test.accounts_mixin import AccountsTestMixin +from erpnext.controllers.accounts_controller import InvalidQtyError, update_child_qty_rate +>>>>>>> 60694e09c4 (test: credit check on Sales Order re-open) from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import ( make_maintenance_schedule, ) @@ -31,7 +36,7 @@ from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry -class TestSalesOrder(FrappeTestCase): +class TestSalesOrder(AccountsTestMixin, FrappeTestCase): @classmethod def setUpClass(cls): super().setUpClass() @@ -52,6 +57,9 @@ class TestSalesOrder(FrappeTestCase): ) super().tearDownClass() + def setUp(self): + self.create_customer("_Test Customer Credit") + def tearDown(self): frappe.set_user("Administrator") @@ -2159,6 +2167,28 @@ class TestSalesOrder(FrappeTestCase): frappe.db.set_single_value("Stock Settings", "update_existing_price_list_rate", 0) frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0) + def test_credit_limit_on_so_reopning(self): + # set credit limit + company = "_Test Company" + customer = frappe.get_doc("Customer", self.customer) + customer.credit_limits = [] + customer.append( + "credit_limits", {"company": company, "credit_limit": 1000, "bypass_credit_limit_check": False} + ) + customer.save() + + so1 = make_sales_order(qty=9, rate=100, do_not_submit=True) + so1.customer = self.customer + so1.save().submit() + + so1.update_status("Closed") + + so2 = make_sales_order(qty=9, rate=100, do_not_submit=True) + so2.customer = self.customer + so2.save().submit() + + self.assertRaises(frappe.ValidationError, so1.update_status, "Draft") + def automatically_fetch_payment_terms(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") From bf36b4fa11da8a9f6bbd959b5bca581cb42bda9f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 1 Jul 2024 17:05:56 +0530 Subject: [PATCH 12/17] chore: resolve conflict --- erpnext/selling/doctype/sales_order/test_sales_order.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 8783f6accda..74f781d8982 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -9,12 +9,8 @@ from frappe.core.doctype.user_permission.test_user_permission import create_user from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, flt, getdate, nowdate, today -<<<<<<< HEAD -from erpnext.controllers.accounts_controller import update_child_qty_rate -======= from erpnext.accounts.test.accounts_mixin import AccountsTestMixin -from erpnext.controllers.accounts_controller import InvalidQtyError, update_child_qty_rate ->>>>>>> 60694e09c4 (test: credit check on Sales Order re-open) +from erpnext.controllers.accounts_controller import update_child_qty_rate from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import ( make_maintenance_schedule, ) From 650b25fc23ae7180a16b974bc75f8bbdc2d7e23c Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 28 Jun 2024 20:28:01 +0530 Subject: [PATCH 13/17] fix: always post to tax account heads if LCV is booked (cherry picked from commit 0fcd5d51309ed3fd2dfcc16d44d31489990848d1) --- .../stock/doctype/purchase_receipt/purchase_receipt.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 7bbe7b9ff75..7b906764946 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -661,6 +661,13 @@ class PurchaseReceipt(BuyingController): posting_date=posting_date, ) + def is_landed_cost_booked_for_any_item(self) -> bool: + for x in self.items: + if x.landed_cost_voucher_amount != 0: + return True + + return False + def make_tax_gl_entries(self, gl_entries, via_landed_cost_voucher=False): negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get("items")]) is_asset_pr = any(d.is_fixed_asset for d in self.get("items")) @@ -696,7 +703,7 @@ class PurchaseReceipt(BuyingController): i = 1 for tax in self.get("taxes"): if valuation_tax.get(tax.name): - if via_landed_cost_voucher: + if via_landed_cost_voucher or self.is_landed_cost_booked_for_any_item(): account = tax.account_head else: negative_expense_booked_in_pi = frappe.db.sql( From 003d7e9f3e9ef68a464ce16f8c075bb8dabffb40 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Mon, 1 Jul 2024 17:50:36 +0530 Subject: [PATCH 14/17] test: Repost should not merge expense accounts from LCV (cherry picked from commit fa56555150a36d2880f0d409c4924ca7bbab121d) # Conflicts: # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py --- .../purchase_receipt/test_purchase_receipt.py | 685 ++++++++++++++++++ 1 file changed, 685 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 52dd85767f8..defecad7a38 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2349,6 +2349,691 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertSequenceEqual(expected_gle, gl_entries) frappe.local.enable_perpetual_inventory["_Test Company"] = old_perpetual_inventory +<<<<<<< HEAD +======= + def test_purchase_receipt_with_use_serial_batch_field_for_rejected_qty(self): + batch_item = make_item( + "_Test Purchase Receipt Batch Item For Rejected Qty", + properties={"has_batch_no": 1, "create_new_batch": 1, "is_stock_item": 1}, + ).name + + serial_item = make_item( + "_Test Purchase Receipt Serial Item for Rejected Qty", + properties={"has_serial_no": 1, "is_stock_item": 1}, + ).name + + rej_warehouse = create_warehouse("_Test Purchase Warehouse For Rejected Qty") + + batch_no = "BATCH-BNU-TPRBI-0001" + serial_nos = ["SNU-TPRSI-0001", "SNU-TPRSI-0002", "SNU-TPRSI-0003"] + + if not frappe.db.exists("Batch", batch_no): + frappe.get_doc( + { + "doctype": "Batch", + "batch_id": batch_no, + "item": batch_item, + } + ).insert() + + for serial_no in serial_nos: + if not frappe.db.exists("Serial No", serial_no): + frappe.get_doc( + { + "doctype": "Serial No", + "item_code": serial_item, + "serial_no": serial_no, + } + ).insert() + + pr = make_purchase_receipt( + item_code=batch_item, + received_qty=10, + qty=8, + rejected_qty=2, + rejected_warehouse=rej_warehouse, + use_serial_batch_fields=1, + batch_no=batch_no, + rate=100, + do_not_submit=1, + ) + + pr.append( + "items", + { + "item_code": serial_item, + "qty": 2, + "rate": 100, + "base_rate": 100, + "item_name": serial_item, + "uom": "Nos", + "stock_uom": "Nos", + "conversion_factor": 1, + "rejected_qty": 1, + "warehouse": pr.items[0].warehouse, + "rejected_warehouse": rej_warehouse, + "use_serial_batch_fields": 1, + "serial_no": "\n".join(serial_nos[:2]), + "rejected_serial_no": serial_nos[2], + }, + ) + + pr.save() + pr.submit() + + pr.reload() + + for row in pr.items: + self.assertTrue(row.serial_and_batch_bundle) + self.assertTrue(row.rejected_serial_and_batch_bundle) + + if row.item_code == batch_item: + self.assertEqual(row.batch_no, batch_no) + else: + self.assertEqual(row.serial_no, "\n".join(serial_nos[:2])) + self.assertEqual(row.rejected_serial_no, serial_nos[2]) + + def test_internal_transfer_with_serial_batch_items_and_their_valuation(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + prepare_data_for_internal_transfer() + + customer = "_Test Internal Customer 2" + company = "_Test Company with perpetual inventory" + + batch_item_doc = make_item( + "_Test Batch Item For Stock Transfer", + {"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "BT-BIFST-.####"}, + ) + + serial_item_doc = make_item( + "_Test Serial No Item For Stock Transfer", + {"has_serial_no": 1, "serial_no_series": "BT-BIFST-.####"}, + ) + + inward_entry = make_purchase_receipt( + item_code=batch_item_doc.name, + qty=10, + rate=150, + warehouse="Stores - TCP1", + company="_Test Company with perpetual inventory", + use_serial_batch_fields=1, + do_not_submit=1, + ) + + inward_entry.append( + "items", + { + "item_code": serial_item_doc.name, + "qty": 15, + "rate": 250, + "item_name": serial_item_doc.item_name, + "conversion_factor": 1.0, + "uom": serial_item_doc.stock_uom, + "stock_uom": serial_item_doc.stock_uom, + "warehouse": "Stores - TCP1", + "use_serial_batch_fields": 1, + }, + ) + + inward_entry.submit() + inward_entry.reload() + + for row in inward_entry.items: + self.assertTrue(row.serial_and_batch_bundle) + + inter_transfer_dn = create_delivery_note( + item_code=inward_entry.items[0].item_code, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=10, + rate=500, + warehouse="Stores - TCP1", + target_warehouse="Work In Progress - TCP1", + batch_no=get_batch_from_bundle(inward_entry.items[0].serial_and_batch_bundle), + use_serial_batch_fields=1, + do_not_submit=1, + ) + + inter_transfer_dn.append( + "items", + { + "item_code": serial_item_doc.name, + "qty": 15, + "rate": 350, + "item_name": serial_item_doc.item_name, + "conversion_factor": 1.0, + "uom": serial_item_doc.stock_uom, + "stock_uom": serial_item_doc.stock_uom, + "warehouse": "Stores - TCP1", + "target_warehouse": "Work In Progress - TCP1", + "serial_no": "\n".join( + get_serial_nos_from_bundle(inward_entry.items[1].serial_and_batch_bundle) + ), + "use_serial_batch_fields": 1, + }, + ) + + inter_transfer_dn.submit() + inter_transfer_dn.reload() + for row in inter_transfer_dn.items: + if row.item_code == batch_item_doc.name: + self.assertEqual(row.rate, 150.0) + else: + self.assertEqual(row.rate, 250.0) + + self.assertTrue(row.serial_and_batch_bundle) + + inter_transfer_pr = make_inter_company_purchase_receipt(inter_transfer_dn.name) + for row in inter_transfer_pr.items: + row.from_warehouse = "Work In Progress - TCP1" + row.warehouse = "Stores - TCP1" + inter_transfer_pr.submit() + + for row in inter_transfer_pr.items: + if row.item_code == batch_item_doc.name: + self.assertEqual(row.rate, 150.0) + else: + self.assertEqual(row.rate, 250.0) + + self.assertTrue(row.serial_and_batch_bundle) + + inter_transfer_pr_return = make_return_doc("Purchase Receipt", inter_transfer_pr.name) + + inter_transfer_pr_return.submit() + inter_transfer_pr_return.reload() + for row in inter_transfer_pr_return.items: + self.assertTrue(row.serial_and_batch_bundle) + if row.item_code == serial_item_doc.name: + self.assertEqual(row.rate, 250.0) + serial_nos = get_serial_nos_from_bundle(row.serial_and_batch_bundle) + for sn in serial_nos: + serial_no_details = frappe.db.get_value( + "Serial No", sn, ["status", "warehouse"], as_dict=1 + ) + self.assertTrue(serial_no_details.status == "Active") + self.assertEqual(serial_no_details.warehouse, "Work In Progress - TCP1") + + inter_transfer_dn_return = make_return_doc("Delivery Note", inter_transfer_dn.name) + inter_transfer_dn_return.posting_date = today() + inter_transfer_dn_return.posting_time = nowtime() + for row in inter_transfer_dn_return.items: + row.target_warehouse = "Work In Progress - TCP1" + + inter_transfer_dn_return.submit() + inter_transfer_dn_return.reload() + + for row in inter_transfer_dn_return.items: + self.assertTrue(row.serial_and_batch_bundle) + + def test_internal_transfer_with_serial_batch_items_without_use_serial_batch_fields(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0) + + prepare_data_for_internal_transfer() + + customer = "_Test Internal Customer 2" + company = "_Test Company with perpetual inventory" + + batch_item_doc = make_item( + "_Test Batch Item For Stock Transfer USE SERIAL BATCH FIELDS", + {"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "USBF-BT-BIFST-.####"}, + ) + + serial_item_doc = make_item( + "_Test Serial No Item For Stock Transfer USE SERIAL BATCH FIELDS", + {"has_serial_no": 1, "serial_no_series": "USBF-BT-BIFST-.####"}, + ) + + inward_entry = make_purchase_receipt( + item_code=batch_item_doc.name, + qty=10, + rate=150, + warehouse="Stores - TCP1", + company="_Test Company with perpetual inventory", + use_serial_batch_fields=0, + do_not_submit=1, + ) + + inward_entry.append( + "items", + { + "item_code": serial_item_doc.name, + "qty": 15, + "rate": 250, + "item_name": serial_item_doc.item_name, + "conversion_factor": 1.0, + "uom": serial_item_doc.stock_uom, + "stock_uom": serial_item_doc.stock_uom, + "warehouse": "Stores - TCP1", + "use_serial_batch_fields": 0, + }, + ) + + inward_entry.submit() + inward_entry.reload() + + for row in inward_entry.items: + self.assertTrue(row.serial_and_batch_bundle) + + inter_transfer_dn = create_delivery_note( + item_code=inward_entry.items[0].item_code, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=10, + rate=500, + warehouse="Stores - TCP1", + target_warehouse="Work In Progress - TCP1", + batch_no=get_batch_from_bundle(inward_entry.items[0].serial_and_batch_bundle), + use_serial_batch_fields=0, + do_not_submit=1, + ) + + inter_transfer_dn.append( + "items", + { + "item_code": serial_item_doc.name, + "qty": 15, + "rate": 350, + "item_name": serial_item_doc.item_name, + "conversion_factor": 1.0, + "uom": serial_item_doc.stock_uom, + "stock_uom": serial_item_doc.stock_uom, + "warehouse": "Stores - TCP1", + "target_warehouse": "Work In Progress - TCP1", + "serial_no": "\n".join( + get_serial_nos_from_bundle(inward_entry.items[1].serial_and_batch_bundle) + ), + "use_serial_batch_fields": 0, + }, + ) + + inter_transfer_dn.submit() + inter_transfer_dn.reload() + for row in inter_transfer_dn.items: + if row.item_code == batch_item_doc.name: + self.assertEqual(row.rate, 150.0) + else: + self.assertEqual(row.rate, 250.0) + + self.assertTrue(row.serial_and_batch_bundle) + + inter_transfer_pr = make_inter_company_purchase_receipt(inter_transfer_dn.name) + for row in inter_transfer_pr.items: + row.from_warehouse = "Work In Progress - TCP1" + row.warehouse = "Stores - TCP1" + inter_transfer_pr.submit() + + for row in inter_transfer_pr.items: + if row.item_code == batch_item_doc.name: + self.assertEqual(row.rate, 150.0) + else: + self.assertEqual(row.rate, 250.0) + + self.assertTrue(row.serial_and_batch_bundle) + + inter_transfer_pr_return = make_return_doc("Purchase Receipt", inter_transfer_pr.name) + + inter_transfer_pr_return.submit() + inter_transfer_pr_return.reload() + for row in inter_transfer_pr_return.items: + self.assertTrue(row.serial_and_batch_bundle) + if row.item_code == serial_item_doc.name: + self.assertEqual(row.rate, 250.0) + serial_nos = get_serial_nos_from_bundle(row.serial_and_batch_bundle) + for sn in serial_nos: + serial_no_details = frappe.db.get_value( + "Serial No", sn, ["status", "warehouse"], as_dict=1 + ) + self.assertTrue(serial_no_details.status == "Active") + self.assertEqual(serial_no_details.warehouse, "Work In Progress - TCP1") + + inter_transfer_dn_return = make_return_doc("Delivery Note", inter_transfer_dn.name) + inter_transfer_dn_return.posting_date = today() + inter_transfer_dn_return.posting_time = nowtime() + for row in inter_transfer_dn_return.items: + row.target_warehouse = "Work In Progress - TCP1" + + inter_transfer_dn_return.submit() + inter_transfer_dn_return.reload() + + for row in inter_transfer_dn_return.items: + self.assertTrue(row.serial_and_batch_bundle) + + frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) + + def test_purchase_receipt_bill_for_rejected_quantity_in_purchase_invoice(self): + item_code = make_item( + "_Test Purchase Receipt Bill For Rejected Quantity", + properties={"is_stock_item": 1}, + ).name + + pr = make_purchase_receipt(item_code=item_code, qty=5, rate=100) + + return_pr = make_purchase_receipt( + item_code=item_code, + 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.submit() + old_value = frappe.db.get_single_value( + "Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice" + ) + + frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0) + pi = make_purchase_invoice(pr.name) + self.assertEqual(pi.items[0].qty, 3) + + frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 1) + pi = make_purchase_invoice(pr.name) + pi.submit() + self.assertEqual(pi.items[0].qty, 5) + + frappe.db.set_single_value( + "Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", old_value + ) + + def test_zero_valuation_rate_for_batched_item(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + item = make_item( + "_Test Zero Valuation Rate For the Batch Item", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TZVRFORBATCH.#####", + "valuation_rate": 200, + }, + ) + + pi = make_purchase_receipt( + qty=10, + rate=0, + item_code=item.name, + ) + + pi.reload() + batch_no = get_batch_from_bundle(pi.items[0].serial_and_batch_bundle) + + se = make_stock_entry( + purpose="Material Issue", + item_code=item.name, + source=pi.items[0].warehouse, + qty=10, + batch_no=batch_no, + use_serial_batch_fields=0, + ) + + se.submit() + + se.reload() + + self.assertEqual(se.items[0].valuation_rate, 0) + self.assertEqual(se.items[0].basic_rate, 0) + + sabb_doc = frappe.get_doc("Serial and Batch Bundle", se.items[0].serial_and_batch_bundle) + for row in sabb_doc.entries: + self.assertEqual(row.incoming_rate, 0) + + def test_purchase_return_from_accepted_and_rejected_warehouse(self): + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( + make_purchase_return, + ) + + item = make_item( + "_Test PR Item With Return From Accepted and Rejected WH", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "SD-TZVRFORBATCH.#####", + "valuation_rate": 200, + }, + ) + + pr = make_purchase_receipt( + qty=10, + rejected_qty=5, + rate=100, + item_code=item.name, + ) + + pr.reload() + self.assertTrue(pr.items[0].serial_and_batch_bundle) + self.assertTrue(pr.items[0].rejected_serial_and_batch_bundle) + + return_pr = make_purchase_return(pr.name) + return_pr.submit() + + return_pr.reload() + self.assertTrue(return_pr.items[0].serial_and_batch_bundle) + self.assertTrue(return_pr.items[0].rejected_serial_and_batch_bundle) + + self.assertEqual( + return_pr.items[0].qty, + frappe.db.get_value( + "Serial and Batch Bundle", return_pr.items[0].serial_and_batch_bundle, "total_qty" + ), + ) + + self.assertEqual( + return_pr.items[0].rejected_qty, + frappe.db.get_value( + "Serial and Batch Bundle", return_pr.items[0].rejected_serial_and_batch_bundle, "total_qty" + ), + ) + + def test_manufacturing_and_expiry_date_for_batch(self): + item = make_item( + "_Test Manufacturing and Expiry Date For Batch", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "B-MEBATCH.#####", + "has_expiry_date": 1, + "shelf_life_in_days": 5, + }, + ) + + pr = make_purchase_receipt( + qty=10, + rate=100, + item_code=item.name, + posting_date=today(), + ) + + pr.reload() + self.assertTrue(pr.items[0].serial_and_batch_bundle) + + batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle) + batch = frappe.get_doc("Batch", batch_no) + self.assertEqual(batch.manufacturing_date, getdate(today())) + self.assertEqual(batch.expiry_date, getdate(add_days(today(), 5))) + + def test_purchase_return_from_rejected_warehouse(self): + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( + make_purchase_return_against_rejected_warehouse, + ) + + item_code = "_Test Item Return from Rejected Warehouse 11" + create_item(item_code) + + warehouse = create_warehouse("_Test Warehouse Return Qty Warehouse 11") + rejected_warehouse = create_warehouse("_Test Rejected Warehouse Return Qty Warehouse 11") + + # Step 1: Create Purchase Receipt with valuation rate 100 + pr = make_purchase_receipt( + item_code=item_code, + warehouse=warehouse, + qty=24, + rate=100, + rejected_qty=31, + rejected_warehouse=rejected_warehouse, + ) + + pr_return = make_purchase_return_against_rejected_warehouse(pr.name) + pr_return.save() + pr_return.submit() + + self.assertEqual(pr_return.items[0].warehouse, rejected_warehouse) + self.assertEqual(pr_return.items[0].qty, 31 * -1) + self.assertEqual(pr_return.items[0].rejected_qty, 0.0) + self.assertEqual(pr_return.items[0].rejected_warehouse, "") + + def test_tax_account_heads_on_lcv_and_item_repost(self): + """ + PO -> PR -> PI + PR -> LCV + Backdated `Repost Item valuation` should not merge tax account heads into stock_rbnb + """ + from erpnext.accounts.doctype.account.test_account import create_account + from erpnext.buying.doctype.purchase_order.test_purchase_order import ( + create_purchase_order, + make_pr_against_po, + ) + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice + + stock_rbnb = "Stock Received But Not Billed - _TC" + stock_in_hand = "Stock In Hand - _TC" + test_cc = "_Test Cost Center - _TC" + test_company = "_Test Company" + creditors = "Creditors - _TC" + lcv_expense_account = "Expenses Included In Valuation - _TC" + + company_doc = frappe.get_doc("Company", test_company) + company_doc.enable_perpetual_inventory = True + company_doc.stock_received_but_not_billed = stock_rbnb + company_doc.save() + + packaging_charges_account = create_account( + account_name="Packaging Charges", + parent_account="Indirect Expenses - _TC", + company=test_company, + account_type="Tax", + ) + + po = create_purchase_order(qty=10, rate=100, do_not_save=1) + po.taxes = [] + po.append( + "taxes", + { + "category": "Valuation and Total", + "account_head": packaging_charges_account, + "cost_center": test_cc, + "description": "Test", + "add_deduct_tax": "Add", + "charge_type": "Actual", + "tax_amount": 250, + }, + ) + po.save().submit() + + pr = make_pr_against_po(po.name, received_qty=10) + pr_gl_entries = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True) + expected_pr_gles = [ + {"account": stock_rbnb, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc}, + {"account": stock_in_hand, "debit": 1250.0, "credit": 0.0, "cost_center": test_cc}, + {"account": packaging_charges_account, "debit": 0.0, "credit": 250.0, "cost_center": test_cc}, + ] + self.assertEqual(expected_pr_gles, pr_gl_entries) + + # Make PI against Purchase Receipt + pi = make_purchase_invoice(pr.name).save().submit() + pi_gl_entries = get_gl_entries(pi.doctype, pi.name, skip_cancelled=True) + expected_pi_gles = [ + {"account": stock_rbnb, "debit": 1000.0, "credit": 0.0, "cost_center": test_cc}, + {"account": packaging_charges_account, "debit": 250.0, "credit": 0.0, "cost_center": test_cc}, + {"account": creditors, "debit": 0.0, "credit": 1250.0, "cost_center": None}, + ] + self.assertEqual(expected_pi_gles, pi_gl_entries) + + self.create_lcv(pr.doctype, pr.name, test_company, lcv_expense_account) + pr_gles_after_lcv = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True) + expected_pr_gles_after_lcv = [ + {"account": stock_rbnb, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc}, + {"account": stock_in_hand, "debit": 1300.0, "credit": 0.0, "cost_center": test_cc}, + {"account": packaging_charges_account, "debit": 0.0, "credit": 250.0, "cost_center": test_cc}, + {"account": lcv_expense_account, "debit": 0.0, "credit": 50.0, "cost_center": test_cc}, + ] + self.assertEqual(expected_pr_gles_after_lcv, pr_gles_after_lcv) + + # Trigger Repost Item Valudation on a older date + repost_doc = frappe.get_doc( + { + "doctype": "Repost Item Valuation", + "based_on": "Item and Warehouse", + "item_code": pr.items[0].item_code, + "warehouse": pr.items[0].warehouse, + "posting_date": add_days(pr.posting_date, -1), + "posting_time": "00:00:00", + "company": pr.company, + "allow_negative_stock": 1, + "via_landed_cost_voucher": 0, + "allow_zero_rate": 0, + } + ) + repost_doc.save().submit() + + pr_gles_after_repost = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True) + expected_pr_gles_after_repost = [ + {"account": stock_rbnb, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc}, + {"account": stock_in_hand, "debit": 1300.0, "credit": 0.0, "cost_center": test_cc}, + {"account": packaging_charges_account, "debit": 0.0, "credit": 250.0, "cost_center": test_cc}, + {"account": lcv_expense_account, "debit": 0.0, "credit": 50.0, "cost_center": test_cc}, + ] + self.assertEqual(len(pr_gles_after_repost), len(expected_pr_gles_after_repost)) + self.assertEqual(expected_pr_gles_after_repost, pr_gles_after_repost) + + def create_lcv(self, receipt_document_type, receipt_document, company, expense_account, charges=50): + ref_doc = frappe.get_doc(receipt_document_type, receipt_document) + + lcv = frappe.new_doc("Landed Cost Voucher") + lcv.company = company + lcv.distribute_charges_based_on = "Qty" + lcv.set( + "purchase_receipts", + [ + { + "receipt_document_type": receipt_document_type, + "receipt_document": receipt_document, + "supplier": ref_doc.supplier, + "posting_date": ref_doc.posting_date, + "grand_total": ref_doc.base_grand_total, + } + ], + ) + + lcv.set( + "taxes", + [ + { + "description": "Testing", + "expense_account": expense_account, + "amount": charges, + } + ], + ) + lcv.save().submit() + return lcv + +>>>>>>> fa56555150 (test: Repost should not merge expense accounts from LCV) def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From f31ed7557855b96e33144ac5bdc839e6d750b852 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 2 Jul 2024 12:45:04 +0530 Subject: [PATCH 15/17] refactor(test): cleanup test data (cherry picked from commit 6ba6b5aa33a6a5d9f3ad042dd9cf2c423960dd55) --- .../purchase_receipt/test_purchase_receipt.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index defecad7a38..f0f17adefe0 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2964,7 +2964,7 @@ class TestPurchaseReceipt(FrappeTestCase): ] self.assertEqual(expected_pi_gles, pi_gl_entries) - self.create_lcv(pr.doctype, pr.name, test_company, lcv_expense_account) + lcv = self.create_lcv(pr.doctype, pr.name, test_company, lcv_expense_account) pr_gles_after_lcv = get_gl_entries(pr.doctype, pr.name, skip_cancelled=True) expected_pr_gles_after_lcv = [ {"account": stock_rbnb, "debit": 0.0, "credit": 1000.0, "cost_center": test_cc}, @@ -3001,6 +3001,18 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(len(pr_gles_after_repost), len(expected_pr_gles_after_repost)) self.assertEqual(expected_pr_gles_after_repost, pr_gles_after_repost) + # teardown + lcv.reload() + lcv.cancel() + pi.reload() + pi.cancel() + pr.reload() + pr.cancel() + + company_doc.enable_perpetual_inventory = False + company_doc.stock_received_but_not_billed = None + company_doc.save() + def create_lcv(self, receipt_document_type, receipt_document, company, expense_account, charges=50): ref_doc = frappe.get_doc(receipt_document_type, receipt_document) From 72b93805fdfd021e25b620a86563d63b90da4389 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 2 Jul 2024 17:00:16 +0530 Subject: [PATCH 16/17] refactor(test): fix flaky test (cherry picked from commit 0e256b8b29d2779bf84c3adb311cc3b122dd4b42) --- erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index f0f17adefe0..6f82eb9e831 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2920,6 +2920,7 @@ class TestPurchaseReceipt(FrappeTestCase): company_doc = frappe.get_doc("Company", test_company) company_doc.enable_perpetual_inventory = True company_doc.stock_received_but_not_billed = stock_rbnb + company_doc.default_inventory_account = stock_in_hand company_doc.save() packaging_charges_account = create_account( @@ -3011,6 +3012,7 @@ class TestPurchaseReceipt(FrappeTestCase): company_doc.enable_perpetual_inventory = False company_doc.stock_received_but_not_billed = None + company_doc.default_inventory_account = None company_doc.save() def create_lcv(self, receipt_document_type, receipt_document, company, expense_account, charges=50): From 4d99449aa87997dee9eaf7eac795205e8066e4eb Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Wed, 3 Jul 2024 07:16:08 +0530 Subject: [PATCH 17/17] chore: resolve conflict --- .../purchase_receipt/test_purchase_receipt.py | 549 ------------------ 1 file changed, 549 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 6f82eb9e831..bb218f4fe7b 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -2349,554 +2349,6 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertSequenceEqual(expected_gle, gl_entries) frappe.local.enable_perpetual_inventory["_Test Company"] = old_perpetual_inventory -<<<<<<< HEAD -======= - def test_purchase_receipt_with_use_serial_batch_field_for_rejected_qty(self): - batch_item = make_item( - "_Test Purchase Receipt Batch Item For Rejected Qty", - properties={"has_batch_no": 1, "create_new_batch": 1, "is_stock_item": 1}, - ).name - - serial_item = make_item( - "_Test Purchase Receipt Serial Item for Rejected Qty", - properties={"has_serial_no": 1, "is_stock_item": 1}, - ).name - - rej_warehouse = create_warehouse("_Test Purchase Warehouse For Rejected Qty") - - batch_no = "BATCH-BNU-TPRBI-0001" - serial_nos = ["SNU-TPRSI-0001", "SNU-TPRSI-0002", "SNU-TPRSI-0003"] - - if not frappe.db.exists("Batch", batch_no): - frappe.get_doc( - { - "doctype": "Batch", - "batch_id": batch_no, - "item": batch_item, - } - ).insert() - - for serial_no in serial_nos: - if not frappe.db.exists("Serial No", serial_no): - frappe.get_doc( - { - "doctype": "Serial No", - "item_code": serial_item, - "serial_no": serial_no, - } - ).insert() - - pr = make_purchase_receipt( - item_code=batch_item, - received_qty=10, - qty=8, - rejected_qty=2, - rejected_warehouse=rej_warehouse, - use_serial_batch_fields=1, - batch_no=batch_no, - rate=100, - do_not_submit=1, - ) - - pr.append( - "items", - { - "item_code": serial_item, - "qty": 2, - "rate": 100, - "base_rate": 100, - "item_name": serial_item, - "uom": "Nos", - "stock_uom": "Nos", - "conversion_factor": 1, - "rejected_qty": 1, - "warehouse": pr.items[0].warehouse, - "rejected_warehouse": rej_warehouse, - "use_serial_batch_fields": 1, - "serial_no": "\n".join(serial_nos[:2]), - "rejected_serial_no": serial_nos[2], - }, - ) - - pr.save() - pr.submit() - - pr.reload() - - for row in pr.items: - self.assertTrue(row.serial_and_batch_bundle) - self.assertTrue(row.rejected_serial_and_batch_bundle) - - if row.item_code == batch_item: - self.assertEqual(row.batch_no, batch_no) - else: - self.assertEqual(row.serial_no, "\n".join(serial_nos[:2])) - self.assertEqual(row.rejected_serial_no, serial_nos[2]) - - def test_internal_transfer_with_serial_batch_items_and_their_valuation(self): - from erpnext.controllers.sales_and_purchase_return import make_return_doc - from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt - from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note - - prepare_data_for_internal_transfer() - - customer = "_Test Internal Customer 2" - company = "_Test Company with perpetual inventory" - - batch_item_doc = make_item( - "_Test Batch Item For Stock Transfer", - {"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "BT-BIFST-.####"}, - ) - - serial_item_doc = make_item( - "_Test Serial No Item For Stock Transfer", - {"has_serial_no": 1, "serial_no_series": "BT-BIFST-.####"}, - ) - - inward_entry = make_purchase_receipt( - item_code=batch_item_doc.name, - qty=10, - rate=150, - warehouse="Stores - TCP1", - company="_Test Company with perpetual inventory", - use_serial_batch_fields=1, - do_not_submit=1, - ) - - inward_entry.append( - "items", - { - "item_code": serial_item_doc.name, - "qty": 15, - "rate": 250, - "item_name": serial_item_doc.item_name, - "conversion_factor": 1.0, - "uom": serial_item_doc.stock_uom, - "stock_uom": serial_item_doc.stock_uom, - "warehouse": "Stores - TCP1", - "use_serial_batch_fields": 1, - }, - ) - - inward_entry.submit() - inward_entry.reload() - - for row in inward_entry.items: - self.assertTrue(row.serial_and_batch_bundle) - - inter_transfer_dn = create_delivery_note( - item_code=inward_entry.items[0].item_code, - company=company, - customer=customer, - cost_center="Main - TCP1", - expense_account="Cost of Goods Sold - TCP1", - qty=10, - rate=500, - warehouse="Stores - TCP1", - target_warehouse="Work In Progress - TCP1", - batch_no=get_batch_from_bundle(inward_entry.items[0].serial_and_batch_bundle), - use_serial_batch_fields=1, - do_not_submit=1, - ) - - inter_transfer_dn.append( - "items", - { - "item_code": serial_item_doc.name, - "qty": 15, - "rate": 350, - "item_name": serial_item_doc.item_name, - "conversion_factor": 1.0, - "uom": serial_item_doc.stock_uom, - "stock_uom": serial_item_doc.stock_uom, - "warehouse": "Stores - TCP1", - "target_warehouse": "Work In Progress - TCP1", - "serial_no": "\n".join( - get_serial_nos_from_bundle(inward_entry.items[1].serial_and_batch_bundle) - ), - "use_serial_batch_fields": 1, - }, - ) - - inter_transfer_dn.submit() - inter_transfer_dn.reload() - for row in inter_transfer_dn.items: - if row.item_code == batch_item_doc.name: - self.assertEqual(row.rate, 150.0) - else: - self.assertEqual(row.rate, 250.0) - - self.assertTrue(row.serial_and_batch_bundle) - - inter_transfer_pr = make_inter_company_purchase_receipt(inter_transfer_dn.name) - for row in inter_transfer_pr.items: - row.from_warehouse = "Work In Progress - TCP1" - row.warehouse = "Stores - TCP1" - inter_transfer_pr.submit() - - for row in inter_transfer_pr.items: - if row.item_code == batch_item_doc.name: - self.assertEqual(row.rate, 150.0) - else: - self.assertEqual(row.rate, 250.0) - - self.assertTrue(row.serial_and_batch_bundle) - - inter_transfer_pr_return = make_return_doc("Purchase Receipt", inter_transfer_pr.name) - - inter_transfer_pr_return.submit() - inter_transfer_pr_return.reload() - for row in inter_transfer_pr_return.items: - self.assertTrue(row.serial_and_batch_bundle) - if row.item_code == serial_item_doc.name: - self.assertEqual(row.rate, 250.0) - serial_nos = get_serial_nos_from_bundle(row.serial_and_batch_bundle) - for sn in serial_nos: - serial_no_details = frappe.db.get_value( - "Serial No", sn, ["status", "warehouse"], as_dict=1 - ) - self.assertTrue(serial_no_details.status == "Active") - self.assertEqual(serial_no_details.warehouse, "Work In Progress - TCP1") - - inter_transfer_dn_return = make_return_doc("Delivery Note", inter_transfer_dn.name) - inter_transfer_dn_return.posting_date = today() - inter_transfer_dn_return.posting_time = nowtime() - for row in inter_transfer_dn_return.items: - row.target_warehouse = "Work In Progress - TCP1" - - inter_transfer_dn_return.submit() - inter_transfer_dn_return.reload() - - for row in inter_transfer_dn_return.items: - self.assertTrue(row.serial_and_batch_bundle) - - def test_internal_transfer_with_serial_batch_items_without_use_serial_batch_fields(self): - from erpnext.controllers.sales_and_purchase_return import make_return_doc - from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt - from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note - - frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0) - - prepare_data_for_internal_transfer() - - customer = "_Test Internal Customer 2" - company = "_Test Company with perpetual inventory" - - batch_item_doc = make_item( - "_Test Batch Item For Stock Transfer USE SERIAL BATCH FIELDS", - {"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "USBF-BT-BIFST-.####"}, - ) - - serial_item_doc = make_item( - "_Test Serial No Item For Stock Transfer USE SERIAL BATCH FIELDS", - {"has_serial_no": 1, "serial_no_series": "USBF-BT-BIFST-.####"}, - ) - - inward_entry = make_purchase_receipt( - item_code=batch_item_doc.name, - qty=10, - rate=150, - warehouse="Stores - TCP1", - company="_Test Company with perpetual inventory", - use_serial_batch_fields=0, - do_not_submit=1, - ) - - inward_entry.append( - "items", - { - "item_code": serial_item_doc.name, - "qty": 15, - "rate": 250, - "item_name": serial_item_doc.item_name, - "conversion_factor": 1.0, - "uom": serial_item_doc.stock_uom, - "stock_uom": serial_item_doc.stock_uom, - "warehouse": "Stores - TCP1", - "use_serial_batch_fields": 0, - }, - ) - - inward_entry.submit() - inward_entry.reload() - - for row in inward_entry.items: - self.assertTrue(row.serial_and_batch_bundle) - - inter_transfer_dn = create_delivery_note( - item_code=inward_entry.items[0].item_code, - company=company, - customer=customer, - cost_center="Main - TCP1", - expense_account="Cost of Goods Sold - TCP1", - qty=10, - rate=500, - warehouse="Stores - TCP1", - target_warehouse="Work In Progress - TCP1", - batch_no=get_batch_from_bundle(inward_entry.items[0].serial_and_batch_bundle), - use_serial_batch_fields=0, - do_not_submit=1, - ) - - inter_transfer_dn.append( - "items", - { - "item_code": serial_item_doc.name, - "qty": 15, - "rate": 350, - "item_name": serial_item_doc.item_name, - "conversion_factor": 1.0, - "uom": serial_item_doc.stock_uom, - "stock_uom": serial_item_doc.stock_uom, - "warehouse": "Stores - TCP1", - "target_warehouse": "Work In Progress - TCP1", - "serial_no": "\n".join( - get_serial_nos_from_bundle(inward_entry.items[1].serial_and_batch_bundle) - ), - "use_serial_batch_fields": 0, - }, - ) - - inter_transfer_dn.submit() - inter_transfer_dn.reload() - for row in inter_transfer_dn.items: - if row.item_code == batch_item_doc.name: - self.assertEqual(row.rate, 150.0) - else: - self.assertEqual(row.rate, 250.0) - - self.assertTrue(row.serial_and_batch_bundle) - - inter_transfer_pr = make_inter_company_purchase_receipt(inter_transfer_dn.name) - for row in inter_transfer_pr.items: - row.from_warehouse = "Work In Progress - TCP1" - row.warehouse = "Stores - TCP1" - inter_transfer_pr.submit() - - for row in inter_transfer_pr.items: - if row.item_code == batch_item_doc.name: - self.assertEqual(row.rate, 150.0) - else: - self.assertEqual(row.rate, 250.0) - - self.assertTrue(row.serial_and_batch_bundle) - - inter_transfer_pr_return = make_return_doc("Purchase Receipt", inter_transfer_pr.name) - - inter_transfer_pr_return.submit() - inter_transfer_pr_return.reload() - for row in inter_transfer_pr_return.items: - self.assertTrue(row.serial_and_batch_bundle) - if row.item_code == serial_item_doc.name: - self.assertEqual(row.rate, 250.0) - serial_nos = get_serial_nos_from_bundle(row.serial_and_batch_bundle) - for sn in serial_nos: - serial_no_details = frappe.db.get_value( - "Serial No", sn, ["status", "warehouse"], as_dict=1 - ) - self.assertTrue(serial_no_details.status == "Active") - self.assertEqual(serial_no_details.warehouse, "Work In Progress - TCP1") - - inter_transfer_dn_return = make_return_doc("Delivery Note", inter_transfer_dn.name) - inter_transfer_dn_return.posting_date = today() - inter_transfer_dn_return.posting_time = nowtime() - for row in inter_transfer_dn_return.items: - row.target_warehouse = "Work In Progress - TCP1" - - inter_transfer_dn_return.submit() - inter_transfer_dn_return.reload() - - for row in inter_transfer_dn_return.items: - self.assertTrue(row.serial_and_batch_bundle) - - frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1) - - def test_purchase_receipt_bill_for_rejected_quantity_in_purchase_invoice(self): - item_code = make_item( - "_Test Purchase Receipt Bill For Rejected Quantity", - properties={"is_stock_item": 1}, - ).name - - pr = make_purchase_receipt(item_code=item_code, qty=5, rate=100) - - return_pr = make_purchase_receipt( - item_code=item_code, - 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.submit() - old_value = frappe.db.get_single_value( - "Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice" - ) - - frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0) - pi = make_purchase_invoice(pr.name) - self.assertEqual(pi.items[0].qty, 3) - - frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 1) - pi = make_purchase_invoice(pr.name) - pi.submit() - self.assertEqual(pi.items[0].qty, 5) - - frappe.db.set_single_value( - "Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", old_value - ) - - def test_zero_valuation_rate_for_batched_item(self): - from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - - item = make_item( - "_Test Zero Valuation Rate For the Batch Item", - { - "is_purchase_item": 1, - "is_stock_item": 1, - "has_batch_no": 1, - "create_new_batch": 1, - "batch_number_series": "TZVRFORBATCH.#####", - "valuation_rate": 200, - }, - ) - - pi = make_purchase_receipt( - qty=10, - rate=0, - item_code=item.name, - ) - - pi.reload() - batch_no = get_batch_from_bundle(pi.items[0].serial_and_batch_bundle) - - se = make_stock_entry( - purpose="Material Issue", - item_code=item.name, - source=pi.items[0].warehouse, - qty=10, - batch_no=batch_no, - use_serial_batch_fields=0, - ) - - se.submit() - - se.reload() - - self.assertEqual(se.items[0].valuation_rate, 0) - self.assertEqual(se.items[0].basic_rate, 0) - - sabb_doc = frappe.get_doc("Serial and Batch Bundle", se.items[0].serial_and_batch_bundle) - for row in sabb_doc.entries: - self.assertEqual(row.incoming_rate, 0) - - def test_purchase_return_from_accepted_and_rejected_warehouse(self): - from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( - make_purchase_return, - ) - - item = make_item( - "_Test PR Item With Return From Accepted and Rejected WH", - { - "is_purchase_item": 1, - "is_stock_item": 1, - "has_batch_no": 1, - "create_new_batch": 1, - "batch_number_series": "SD-TZVRFORBATCH.#####", - "valuation_rate": 200, - }, - ) - - pr = make_purchase_receipt( - qty=10, - rejected_qty=5, - rate=100, - item_code=item.name, - ) - - pr.reload() - self.assertTrue(pr.items[0].serial_and_batch_bundle) - self.assertTrue(pr.items[0].rejected_serial_and_batch_bundle) - - return_pr = make_purchase_return(pr.name) - return_pr.submit() - - return_pr.reload() - self.assertTrue(return_pr.items[0].serial_and_batch_bundle) - self.assertTrue(return_pr.items[0].rejected_serial_and_batch_bundle) - - self.assertEqual( - return_pr.items[0].qty, - frappe.db.get_value( - "Serial and Batch Bundle", return_pr.items[0].serial_and_batch_bundle, "total_qty" - ), - ) - - self.assertEqual( - return_pr.items[0].rejected_qty, - frappe.db.get_value( - "Serial and Batch Bundle", return_pr.items[0].rejected_serial_and_batch_bundle, "total_qty" - ), - ) - - def test_manufacturing_and_expiry_date_for_batch(self): - item = make_item( - "_Test Manufacturing and Expiry Date For Batch", - { - "is_purchase_item": 1, - "is_stock_item": 1, - "has_batch_no": 1, - "create_new_batch": 1, - "batch_number_series": "B-MEBATCH.#####", - "has_expiry_date": 1, - "shelf_life_in_days": 5, - }, - ) - - pr = make_purchase_receipt( - qty=10, - rate=100, - item_code=item.name, - posting_date=today(), - ) - - pr.reload() - self.assertTrue(pr.items[0].serial_and_batch_bundle) - - batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle) - batch = frappe.get_doc("Batch", batch_no) - self.assertEqual(batch.manufacturing_date, getdate(today())) - self.assertEqual(batch.expiry_date, getdate(add_days(today(), 5))) - - def test_purchase_return_from_rejected_warehouse(self): - from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( - make_purchase_return_against_rejected_warehouse, - ) - - item_code = "_Test Item Return from Rejected Warehouse 11" - create_item(item_code) - - warehouse = create_warehouse("_Test Warehouse Return Qty Warehouse 11") - rejected_warehouse = create_warehouse("_Test Rejected Warehouse Return Qty Warehouse 11") - - # Step 1: Create Purchase Receipt with valuation rate 100 - pr = make_purchase_receipt( - item_code=item_code, - warehouse=warehouse, - qty=24, - rate=100, - rejected_qty=31, - rejected_warehouse=rejected_warehouse, - ) - - pr_return = make_purchase_return_against_rejected_warehouse(pr.name) - pr_return.save() - pr_return.submit() - - self.assertEqual(pr_return.items[0].warehouse, rejected_warehouse) - self.assertEqual(pr_return.items[0].qty, 31 * -1) - self.assertEqual(pr_return.items[0].rejected_qty, 0.0) - self.assertEqual(pr_return.items[0].rejected_warehouse, "") - def test_tax_account_heads_on_lcv_and_item_repost(self): """ PO -> PR -> PI @@ -3047,7 +2499,6 @@ class TestPurchaseReceipt(FrappeTestCase): lcv.save().submit() return lcv ->>>>>>> fa56555150 (test: Repost should not merge expense accounts from LCV) def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier