mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-04 20:59:11 +00:00
Merge branch 'version-14-hotfix' of https://github.com/frappe/erpnext into default_dates_in_reports
This commit is contained in:
@@ -17,9 +17,9 @@ install_docs = [
|
||||
|
||||
|
||||
def get_warehouse_account_map(company=None):
|
||||
company_warehouse_account_map = company and frappe.flags.setdefault(
|
||||
"warehouse_account_map", {}
|
||||
).get(company)
|
||||
company_warehouse_account_map = company and frappe.flags.setdefault("warehouse_account_map", {}).get(
|
||||
company
|
||||
)
|
||||
warehouse_account_map = frappe.flags.warehouse_account_map
|
||||
|
||||
if not warehouse_account_map or not company_warehouse_account_map or frappe.flags.in_test:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
frappe.provide('erpnext.stock');
|
||||
frappe.provide("erpnext.stock");
|
||||
|
||||
erpnext.stock.ItemDashboard = class ItemDashboard {
|
||||
constructor(opts) {
|
||||
@@ -9,46 +9,51 @@ erpnext.stock.ItemDashboard = class ItemDashboard {
|
||||
var me = this;
|
||||
this.start = 0;
|
||||
if (!this.sort_by) {
|
||||
this.sort_by = 'projected_qty';
|
||||
this.sort_order = 'asc';
|
||||
this.sort_by = "projected_qty";
|
||||
this.sort_order = "asc";
|
||||
}
|
||||
|
||||
this.content = $(frappe.render_template('item_dashboard')).appendTo(this.parent);
|
||||
this.result = this.content.find('.result');
|
||||
this.content = $(frappe.render_template("item_dashboard")).appendTo(this.parent);
|
||||
this.result = this.content.find(".result");
|
||||
|
||||
this.content.on('click', '.btn-move', function () {
|
||||
this.content.on("click", ".btn-move", function () {
|
||||
handle_move_add($(this), "Move");
|
||||
});
|
||||
|
||||
this.content.on('click', '.btn-add', function () {
|
||||
this.content.on("click", ".btn-add", function () {
|
||||
handle_move_add($(this), "Add");
|
||||
});
|
||||
|
||||
this.content.on('click', '.btn-edit', function () {
|
||||
let item = unescape($(this).attr('data-item'));
|
||||
let warehouse = unescape($(this).attr('data-warehouse'));
|
||||
let company = unescape($(this).attr('data-company'));
|
||||
frappe.db.get_value('Putaway Rule', {
|
||||
'item_code': item,
|
||||
'warehouse': warehouse,
|
||||
'company': company
|
||||
}, 'name', (r) => {
|
||||
frappe.set_route("Form", "Putaway Rule", r.name);
|
||||
});
|
||||
this.content.on("click", ".btn-edit", function () {
|
||||
let item = unescape($(this).attr("data-item"));
|
||||
let warehouse = unescape($(this).attr("data-warehouse"));
|
||||
let company = unescape($(this).attr("data-company"));
|
||||
frappe.db.get_value(
|
||||
"Putaway Rule",
|
||||
{
|
||||
item_code: item,
|
||||
warehouse: warehouse,
|
||||
company: company,
|
||||
},
|
||||
"name",
|
||||
(r) => {
|
||||
frappe.set_route("Form", "Putaway Rule", r.name);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
function handle_move_add(element, action) {
|
||||
let item = unescape(element.attr('data-item'));
|
||||
let warehouse = unescape(element.attr('data-warehouse'));
|
||||
let actual_qty = unescape(element.attr('data-actual_qty'));
|
||||
let disable_quick_entry = Number(unescape(element.attr('data-disable_quick_entry')));
|
||||
let item = unescape(element.attr("data-item"));
|
||||
let warehouse = unescape(element.attr("data-warehouse"));
|
||||
let actual_qty = unescape(element.attr("data-actual_qty"));
|
||||
let disable_quick_entry = Number(unescape(element.attr("data-disable_quick_entry")));
|
||||
let entry_type = action === "Move" ? "Material Transfer" : "Material Receipt";
|
||||
|
||||
if (disable_quick_entry) {
|
||||
open_stock_entry(item, warehouse, entry_type);
|
||||
} else {
|
||||
if (action === "Add") {
|
||||
let rate = unescape($(this).attr('data-rate'));
|
||||
let rate = unescape($(this).attr("data-rate"));
|
||||
erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function () {
|
||||
me.refresh();
|
||||
});
|
||||
@@ -61,35 +66,33 @@ erpnext.stock.ItemDashboard = class ItemDashboard {
|
||||
}
|
||||
|
||||
function open_stock_entry(item, warehouse, entry_type) {
|
||||
frappe.model.with_doctype('Stock Entry', function () {
|
||||
var doc = frappe.model.get_new_doc('Stock Entry');
|
||||
frappe.model.with_doctype("Stock Entry", function () {
|
||||
var doc = frappe.model.get_new_doc("Stock Entry");
|
||||
if (entry_type) {
|
||||
doc.stock_entry_type = entry_type;
|
||||
}
|
||||
|
||||
var row = frappe.model.add_child(doc, 'items');
|
||||
var row = frappe.model.add_child(doc, "items");
|
||||
row.item_code = item;
|
||||
|
||||
if (entry_type === "Material Transfer") {
|
||||
row.s_warehouse = warehouse;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
row.t_warehouse = warehouse;
|
||||
}
|
||||
|
||||
frappe.set_route('Form', doc.doctype, doc.name);
|
||||
frappe.set_route("Form", doc.doctype, doc.name);
|
||||
});
|
||||
}
|
||||
|
||||
// more
|
||||
this.content.find('.btn-more').on('click', function () {
|
||||
this.content.find(".btn-more").on("click", function () {
|
||||
me.start += me.page_length;
|
||||
me.refresh();
|
||||
});
|
||||
|
||||
}
|
||||
refresh() {
|
||||
if(this.before_refresh) {
|
||||
if (this.before_refresh) {
|
||||
this.before_refresh();
|
||||
}
|
||||
|
||||
@@ -101,7 +104,7 @@ erpnext.stock.ItemDashboard = class ItemDashboard {
|
||||
company: this.company,
|
||||
start: this.start,
|
||||
sort_by: this.sort_by,
|
||||
sort_order: this.sort_order
|
||||
sort_order: this.sort_order,
|
||||
};
|
||||
|
||||
var me = this;
|
||||
@@ -110,11 +113,11 @@ erpnext.stock.ItemDashboard = class ItemDashboard {
|
||||
args: args,
|
||||
callback: function (r) {
|
||||
me.render(r.message);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
render(data) {
|
||||
if (this.start===0) {
|
||||
if (this.start === 0) {
|
||||
this.max_count = 0;
|
||||
this.result.empty();
|
||||
}
|
||||
@@ -129,22 +132,22 @@ erpnext.stock.ItemDashboard = class ItemDashboard {
|
||||
this.max_count = this.max_count;
|
||||
|
||||
// show more button
|
||||
if (data && data.length === (this.page_length + 1)) {
|
||||
this.content.find('.more').removeClass('hidden');
|
||||
if (data && data.length === this.page_length + 1) {
|
||||
this.content.find(".more").removeClass("hidden");
|
||||
|
||||
// remove the last element
|
||||
data.splice(-1);
|
||||
} else {
|
||||
this.content.find('.more').addClass('hidden');
|
||||
this.content.find(".more").addClass("hidden");
|
||||
}
|
||||
|
||||
// If not any stock in any warehouses provide a message to end user
|
||||
if (context.data.length > 0) {
|
||||
this.content.find('.result').css('text-align', 'unset');
|
||||
this.content.find(".result").css("text-align", "unset");
|
||||
$(frappe.render_template(this.template, context)).appendTo(this.result);
|
||||
} else {
|
||||
var message = __("No Stock Available Currently");
|
||||
this.content.find('.result').css('text-align', 'center');
|
||||
this.content.find(".result").css("text-align", "center");
|
||||
|
||||
$(`<div class='text-muted' style='margin: 20px 5px;'>
|
||||
${message} </div>`).appendTo(this.result);
|
||||
@@ -152,19 +155,23 @@ erpnext.stock.ItemDashboard = class ItemDashboard {
|
||||
}
|
||||
|
||||
get_item_dashboard_data(data, max_count, show_item) {
|
||||
if(!max_count) max_count = 0;
|
||||
if(!data) data = [];
|
||||
if (!max_count) max_count = 0;
|
||||
if (!data) data = [];
|
||||
|
||||
data.forEach(function (d) {
|
||||
d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract;
|
||||
d.actual_or_pending =
|
||||
d.projected_qty +
|
||||
d.reserved_qty +
|
||||
d.reserved_qty_for_production +
|
||||
d.reserved_qty_for_sub_contract;
|
||||
d.pending_qty = 0;
|
||||
d.total_reserved = d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract;
|
||||
d.total_reserved =
|
||||
d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract;
|
||||
if (d.actual_or_pending > d.actual_qty) {
|
||||
d.pending_qty = d.actual_or_pending - d.actual_qty;
|
||||
}
|
||||
|
||||
max_count = Math.max(d.actual_or_pending, d.actual_qty,
|
||||
d.total_reserved, max_count);
|
||||
max_count = Math.max(d.actual_or_pending, d.actual_qty, d.total_reserved, max_count);
|
||||
});
|
||||
|
||||
let can_write = 0;
|
||||
@@ -176,7 +183,7 @@ erpnext.stock.ItemDashboard = class ItemDashboard {
|
||||
data: data,
|
||||
max_count: max_count,
|
||||
can_write: can_write,
|
||||
show_item: show_item || false
|
||||
show_item: show_item || false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -201,73 +208,74 @@ erpnext.stock.ItemDashboard = class ItemDashboard {
|
||||
|
||||
erpnext.stock.move_item = function (item, source, target, actual_qty, rate, callback) {
|
||||
var dialog = new frappe.ui.Dialog({
|
||||
title: target ? __('Add Item') : __('Move Item'),
|
||||
fields: [{
|
||||
fieldname: 'item_code',
|
||||
label: __('Item'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Item',
|
||||
read_only: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'source',
|
||||
label: __('Source Warehouse'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Warehouse',
|
||||
read_only: 1
|
||||
},
|
||||
{
|
||||
fieldname: 'target',
|
||||
label: __('Target Warehouse'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Warehouse',
|
||||
reqd: 1,
|
||||
get_query() {
|
||||
return {
|
||||
filters: {
|
||||
is_group: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldname: 'qty',
|
||||
label: __('Quantity'),
|
||||
reqd: 1,
|
||||
fieldtype: 'Float',
|
||||
description: __('Available {0}', [actual_qty])
|
||||
},
|
||||
{
|
||||
fieldname: 'rate',
|
||||
label: __('Rate'),
|
||||
fieldtype: 'Currency',
|
||||
hidden: 1
|
||||
},
|
||||
title: target ? __("Add Item") : __("Move Item"),
|
||||
fields: [
|
||||
{
|
||||
fieldname: "item_code",
|
||||
label: __("Item"),
|
||||
fieldtype: "Link",
|
||||
options: "Item",
|
||||
read_only: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "source",
|
||||
label: __("Source Warehouse"),
|
||||
fieldtype: "Link",
|
||||
options: "Warehouse",
|
||||
read_only: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "target",
|
||||
label: __("Target Warehouse"),
|
||||
fieldtype: "Link",
|
||||
options: "Warehouse",
|
||||
reqd: 1,
|
||||
get_query() {
|
||||
return {
|
||||
filters: {
|
||||
is_group: 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldname: "qty",
|
||||
label: __("Quantity"),
|
||||
reqd: 1,
|
||||
fieldtype: "Float",
|
||||
description: __("Available {0}", [actual_qty]),
|
||||
},
|
||||
{
|
||||
fieldname: "rate",
|
||||
label: __("Rate"),
|
||||
fieldtype: "Currency",
|
||||
hidden: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
dialog.show();
|
||||
dialog.get_field('item_code').set_input(item);
|
||||
dialog.get_field("item_code").set_input(item);
|
||||
|
||||
if (source) {
|
||||
dialog.get_field('source').set_input(source);
|
||||
dialog.get_field("source").set_input(source);
|
||||
} else {
|
||||
dialog.get_field('source').df.hidden = 1;
|
||||
dialog.get_field('source').refresh();
|
||||
dialog.get_field("source").df.hidden = 1;
|
||||
dialog.get_field("source").refresh();
|
||||
}
|
||||
|
||||
if (rate) {
|
||||
dialog.get_field('rate').set_value(rate);
|
||||
dialog.get_field('rate').df.hidden = 0;
|
||||
dialog.get_field('rate').refresh();
|
||||
dialog.get_field("rate").set_value(rate);
|
||||
dialog.get_field("rate").df.hidden = 0;
|
||||
dialog.get_field("rate").refresh();
|
||||
}
|
||||
|
||||
if (target) {
|
||||
dialog.get_field('target').df.read_only = 1;
|
||||
dialog.get_field('target').value = target;
|
||||
dialog.get_field('target').refresh();
|
||||
dialog.get_field("target").df.read_only = 1;
|
||||
dialog.get_field("target").value = target;
|
||||
dialog.get_field("target").refresh();
|
||||
}
|
||||
|
||||
dialog.set_primary_action(__('Create Stock Entry'), function () {
|
||||
dialog.set_primary_action(__("Create Stock Entry"), function () {
|
||||
if (source && (dialog.get_value("qty") == 0 || dialog.get_value("qty") > actual_qty)) {
|
||||
frappe.msgprint(__("Quantity must be greater than zero, and less or equal to {0}", [actual_qty]));
|
||||
return;
|
||||
@@ -278,20 +286,20 @@ erpnext.stock.move_item = function (item, source, target, actual_qty, rate, call
|
||||
return;
|
||||
}
|
||||
|
||||
frappe.model.with_doctype('Stock Entry', function () {
|
||||
let doc = frappe.model.get_new_doc('Stock Entry');
|
||||
doc.from_warehouse = dialog.get_value('source');
|
||||
doc.to_warehouse = dialog.get_value('target');
|
||||
frappe.model.with_doctype("Stock Entry", function () {
|
||||
let doc = frappe.model.get_new_doc("Stock Entry");
|
||||
doc.from_warehouse = dialog.get_value("source");
|
||||
doc.to_warehouse = dialog.get_value("target");
|
||||
doc.stock_entry_type = doc.from_warehouse ? "Material Transfer" : "Material Receipt";
|
||||
let row = frappe.model.add_child(doc, 'items');
|
||||
row.item_code = dialog.get_value('item_code');
|
||||
row.s_warehouse = dialog.get_value('source');
|
||||
row.t_warehouse = dialog.get_value('target');
|
||||
row.qty = dialog.get_value('qty');
|
||||
let row = frappe.model.add_child(doc, "items");
|
||||
row.item_code = dialog.get_value("item_code");
|
||||
row.s_warehouse = dialog.get_value("source");
|
||||
row.t_warehouse = dialog.get_value("target");
|
||||
row.qty = dialog.get_value("qty");
|
||||
row.conversion_factor = 1;
|
||||
row.transfer_qty = dialog.get_value('qty');
|
||||
row.basic_rate = dialog.get_value('rate');
|
||||
frappe.set_route('Form', doc.doctype, doc.name);
|
||||
row.transfer_qty = dialog.get_value("qty");
|
||||
row.basic_rate = dialog.get_value("rate");
|
||||
frappe.set_route("Form", doc.doctype, doc.name);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
frappe.provide('frappe.dashboards.chart_sources');
|
||||
frappe.provide("frappe.dashboards.chart_sources");
|
||||
|
||||
frappe.dashboards.chart_sources["Warehouse wise Stock Value"] = {
|
||||
method: "erpnext.stock.dashboard_chart_source.warehouse_wise_stock_value.warehouse_wise_stock_value.get",
|
||||
@@ -8,7 +8,7 @@ frappe.dashboards.chart_sources["Warehouse wise Stock Value"] = {
|
||||
label: __("Company"),
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
default: frappe.defaults.get_user_default("Company")
|
||||
}
|
||||
]
|
||||
default: frappe.defaults.get_user_default("Company"),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -27,9 +27,7 @@ def get(
|
||||
if filters and filters.get("company"):
|
||||
warehouse_filters.append(["company", "=", filters.get("company")])
|
||||
|
||||
warehouses = frappe.get_list(
|
||||
"Warehouse", pluck="name", filters=warehouse_filters, order_by="name"
|
||||
)
|
||||
warehouses = frappe.get_list("Warehouse", pluck="name", filters=warehouse_filters, order_by="name")
|
||||
|
||||
warehouses = frappe.get_list(
|
||||
"Bin",
|
||||
|
||||
@@ -1,62 +1,72 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.ui.form.on('Batch', {
|
||||
frappe.ui.form.on("Batch", {
|
||||
setup: (frm) => {
|
||||
frm.fields_dict['item'].get_query = function(doc, cdt, cdn) {
|
||||
frm.fields_dict["item"].get_query = function (doc, cdt, cdn) {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters:{
|
||||
'is_stock_item': 1,
|
||||
'has_batch_no': 1
|
||||
}
|
||||
}
|
||||
}
|
||||
filters: {
|
||||
is_stock_item: 1,
|
||||
has_batch_no: 1,
|
||||
},
|
||||
};
|
||||
};
|
||||
},
|
||||
refresh: (frm) => {
|
||||
if(!frm.is_new()) {
|
||||
if (!frm.is_new()) {
|
||||
frm.add_custom_button(__("View Ledger"), () => {
|
||||
frappe.route_options = {
|
||||
batch_no: frm.doc.name
|
||||
batch_no: frm.doc.name,
|
||||
};
|
||||
frappe.set_route("query-report", "Stock Ledger");
|
||||
});
|
||||
frm.trigger('make_dashboard');
|
||||
frm.trigger("make_dashboard");
|
||||
}
|
||||
},
|
||||
item: (frm) => {
|
||||
// frappe.db.get_value('Item', {name: frm.doc.item}, 'has_expiry_date', (r) => {
|
||||
// frm.toggle_reqd('expiry_date', r.has_expiry_date);
|
||||
// });
|
||||
frappe.db.get_value('Item', {name: frm.doc.item}, ['shelf_life_in_days', 'has_expiry_date'], (r) => {
|
||||
if (r.has_expiry_date && r.shelf_life_in_days) {
|
||||
// Calculate expiry date based on shelf_life_in_days
|
||||
frm.set_value('expiry_date', frappe.datetime.add_days(frm.doc.manufacturing_date, r.shelf_life_in_days));
|
||||
}else if(r.has_expiry_date){
|
||||
frm.toggle_reqd('expiry_date', r.has_expiry_date);
|
||||
frappe.db.get_value(
|
||||
"Item",
|
||||
{ name: frm.doc.item },
|
||||
["shelf_life_in_days", "has_expiry_date"],
|
||||
(r) => {
|
||||
if (r.has_expiry_date && r.shelf_life_in_days) {
|
||||
// Calculate expiry date based on shelf_life_in_days
|
||||
frm.set_value(
|
||||
"expiry_date",
|
||||
frappe.datetime.add_days(frm.doc.manufacturing_date, r.shelf_life_in_days)
|
||||
);
|
||||
} else if (r.has_expiry_date) {
|
||||
frm.toggle_reqd("expiry_date", r.has_expiry_date);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
make_dashboard: (frm) => {
|
||||
if(!frm.is_new()) {
|
||||
if (!frm.is_new()) {
|
||||
frappe.call({
|
||||
method: 'erpnext.stock.doctype.batch.batch.get_batch_qty',
|
||||
args: {batch_no: frm.doc.name},
|
||||
method: "erpnext.stock.doctype.batch.batch.get_batch_qty",
|
||||
args: { batch_no: frm.doc.name },
|
||||
callback: (r) => {
|
||||
if(!r.message) {
|
||||
if (!r.message) {
|
||||
return;
|
||||
}
|
||||
|
||||
const section = frm.dashboard.add_section('', __("Stock Levels"));
|
||||
const section = frm.dashboard.add_section("", __("Stock Levels"));
|
||||
|
||||
// sort by qty
|
||||
r.message.sort(function(a, b) { a.qty > b.qty ? 1 : -1 });
|
||||
r.message.sort(function (a, b) {
|
||||
a.qty > b.qty ? 1 : -1;
|
||||
});
|
||||
|
||||
var rows = $('<div></div>').appendTo(section);
|
||||
const rows = $("<div></div>").appendTo(section);
|
||||
|
||||
// show
|
||||
(r.message || []).forEach(function(d) {
|
||||
if(d.qty > 0) {
|
||||
(r.message || []).forEach(function (d) {
|
||||
if (d.qty > 0) {
|
||||
$(`<div class='row' style='margin-bottom: 10px;'>
|
||||
<div class='col-sm-3 small' style='padding-top: 3px;'>${d.warehouse}</div>
|
||||
<div class='col-sm-3 small text-right' style='padding-top: 3px;'>${d.qty}</div>
|
||||
@@ -64,101 +74,110 @@ frappe.ui.form.on('Batch', {
|
||||
<button class='btn btn-default btn-xs btn-move' style='margin-right: 7px;'
|
||||
data-qty = "${d.qty}"
|
||||
data-warehouse = "${d.warehouse}">
|
||||
${__('Move')}</button>
|
||||
${__("Move")}</button>
|
||||
<button class='btn btn-default btn-xs btn-split'
|
||||
data-qty = "${d.qty}"
|
||||
data-warehouse = "${d.warehouse}">
|
||||
${__('Split')}</button>
|
||||
${__("Split")}</button>
|
||||
</div>
|
||||
</div>`).appendTo(rows);
|
||||
}
|
||||
});
|
||||
|
||||
// move - ask for target warehouse and make stock entry
|
||||
rows.find('.btn-move').on('click', function() {
|
||||
var $btn = $(this);
|
||||
rows.find(".btn-move").on("click", function () {
|
||||
const $btn = $(this);
|
||||
const fields = [
|
||||
{
|
||||
fieldname: 'to_warehouse',
|
||||
label: __('To Warehouse'),
|
||||
fieldtype: 'Link',
|
||||
options: 'Warehouse'
|
||||
}
|
||||
fieldname: "to_warehouse",
|
||||
label: __("To Warehouse"),
|
||||
fieldtype: "Link",
|
||||
options: "Warehouse",
|
||||
},
|
||||
];
|
||||
|
||||
frappe.prompt(
|
||||
fields,
|
||||
(data) => {
|
||||
frappe.call({
|
||||
method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry',
|
||||
method: "erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry",
|
||||
args: {
|
||||
item_code: frm.doc.item,
|
||||
batch_no: frm.doc.name,
|
||||
qty: $btn.attr('data-qty'),
|
||||
from_warehouse: $btn.attr('data-warehouse'),
|
||||
qty: $btn.attr("data-qty"),
|
||||
from_warehouse: $btn.attr("data-warehouse"),
|
||||
to_warehouse: data.to_warehouse,
|
||||
source_document: frm.doc.reference_name,
|
||||
reference_doctype: frm.doc.reference_doctype
|
||||
reference_doctype: frm.doc.reference_doctype,
|
||||
},
|
||||
callback: (r) => {
|
||||
frappe.show_alert(__('Stock Entry {0} created',
|
||||
['<a href="/app/stock-entry/'+r.message.name+'">' + r.message.name+ '</a>']));
|
||||
frappe.show_alert(
|
||||
__("Stock Entry {0} created", [
|
||||
'<a href="/app/stock-entry/' +
|
||||
r.message.name +
|
||||
'">' +
|
||||
r.message.name +
|
||||
"</a>",
|
||||
])
|
||||
);
|
||||
frm.refresh();
|
||||
},
|
||||
});
|
||||
},
|
||||
__('Select Target Warehouse'),
|
||||
__('Move')
|
||||
__("Select Target Warehouse"),
|
||||
__("Move")
|
||||
);
|
||||
});
|
||||
|
||||
// split - ask for new qty and batch ID (optional)
|
||||
// and make stock entry via batch.batch_split
|
||||
rows.find('.btn-split').on('click', function() {
|
||||
var $btn = $(this);
|
||||
frappe.prompt([{
|
||||
fieldname: 'qty',
|
||||
label: __('New Batch Qty'),
|
||||
fieldtype: 'Float',
|
||||
'default': $btn.attr('data-qty')
|
||||
},
|
||||
{
|
||||
fieldname: 'new_batch_id',
|
||||
label: __('New Batch ID (Optional)'),
|
||||
fieldtype: 'Data',
|
||||
}],
|
||||
(data) => {
|
||||
frappe.call({
|
||||
method: 'erpnext.stock.doctype.batch.batch.split_batch',
|
||||
args: {
|
||||
item_code: frm.doc.item,
|
||||
batch_no: frm.doc.name,
|
||||
qty: data.qty,
|
||||
warehouse: $btn.attr('data-warehouse'),
|
||||
new_batch_id: data.new_batch_id
|
||||
rows.find(".btn-split").on("click", function () {
|
||||
const $btn = $(this);
|
||||
frappe.prompt(
|
||||
[
|
||||
{
|
||||
fieldname: "qty",
|
||||
label: __("New Batch Qty"),
|
||||
fieldtype: "Float",
|
||||
default: $btn.attr("data-qty"),
|
||||
},
|
||||
callback: (r) => {
|
||||
frm.refresh();
|
||||
{
|
||||
fieldname: "new_batch_id",
|
||||
label: __("New Batch ID (Optional)"),
|
||||
fieldtype: "Data",
|
||||
},
|
||||
});
|
||||
},
|
||||
__('Split Batch'),
|
||||
__('Split')
|
||||
],
|
||||
(data) => {
|
||||
frappe
|
||||
.xcall("erpnext.stock.doctype.batch.batch.split_batch", {
|
||||
item_code: frm.doc.item,
|
||||
batch_no: frm.doc.name,
|
||||
qty: data.qty,
|
||||
warehouse: $btn.attr("data-warehouse"),
|
||||
new_batch_id: data.new_batch_id,
|
||||
})
|
||||
.then(() => frm.reload_doc());
|
||||
},
|
||||
__("Split Batch"),
|
||||
__("Split")
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
frm.dashboard.show();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Batch', 'manufacturing_date', function (frm){
|
||||
frappe.db.get_value('Item', {name: frm.doc.item}, ['shelf_life_in_days', 'has_expiry_date'], (r) => {
|
||||
frappe.ui.form.on("Batch", "manufacturing_date", function (frm) {
|
||||
frappe.db.get_value("Item", { name: frm.doc.item }, ["shelf_life_in_days", "has_expiry_date"], (r) => {
|
||||
if (r.has_expiry_date && r.shelf_life_in_days) {
|
||||
// Calculate expiry date based on shelf_life_in_days
|
||||
frm.set_value('expiry_date', frappe.datetime.add_days(frm.doc.manufacturing_date, r.shelf_life_in_days));
|
||||
frm.set_value(
|
||||
"expiry_date",
|
||||
frappe.datetime.add_days(frm.doc.manufacturing_date, r.shelf_life_in_days)
|
||||
);
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
@@ -165,9 +165,7 @@ class Batch(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_batch_qty(
|
||||
batch_no=None, warehouse=None, item_code=None, posting_date=None, posting_time=None
|
||||
):
|
||||
def get_batch_qty(batch_no=None, warehouse=None, item_code=None, posting_date=None, posting_time=None):
|
||||
"""Returns batch actual qty if warehouse is passed,
|
||||
or returns dict of qty by warehouse if warehouse is None
|
||||
|
||||
@@ -221,9 +219,7 @@ def get_batch_qty(
|
||||
def get_batches_by_oldest(item_code, warehouse):
|
||||
"""Returns the oldest batch and qty for the given item_code and warehouse"""
|
||||
batches = get_batch_qty(item_code=item_code, warehouse=warehouse)
|
||||
batches_dates = [
|
||||
[batch, frappe.get_value("Batch", batch.batch_no, "expiry_date")] for batch in batches
|
||||
]
|
||||
batches_dates = [[batch, frappe.get_value("Batch", batch.batch_no, "expiry_date")] for batch in batches]
|
||||
batches_dates.sort(key=lambda tup: tup[1])
|
||||
return batches_dates
|
||||
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
frappe.listview_settings['Batch'] = {
|
||||
frappe.listview_settings["Batch"] = {
|
||||
add_fields: ["item", "expiry_date", "batch_qty", "disabled"],
|
||||
get_indicator: (doc) => {
|
||||
if (doc.disabled) {
|
||||
return [__("Disabled"), "gray", "disabled,=,1"];
|
||||
} else if (!doc.batch_qty) {
|
||||
return [__("Empty"), "gray", "batch_qty,=,0|disabled,=,0"];
|
||||
} else if (doc.expiry_date && frappe.datetime.get_diff(doc.expiry_date, frappe.datetime.nowdate()) <= 0) {
|
||||
return [__("Expired"), "red", "expiry_date,not in,|expiry_date,<=,Today|batch_qty,>,0|disabled,=,0"]
|
||||
} else if (
|
||||
doc.expiry_date &&
|
||||
frappe.datetime.get_diff(doc.expiry_date, frappe.datetime.nowdate()) <= 0
|
||||
) {
|
||||
return [
|
||||
__("Expired"),
|
||||
"red",
|
||||
"expiry_date,not in,|expiry_date,<=,Today|batch_qty,>,0|disabled,=,0",
|
||||
];
|
||||
} else {
|
||||
return [__("Active"), "green", "batch_qty,>,0|disabled,=,0"];
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -81,9 +81,7 @@ class TestBatch(FrappeTestCase):
|
||||
stock_entry.submit()
|
||||
|
||||
self.assertTrue(stock_entry.items[0].batch_no)
|
||||
self.assertEqual(
|
||||
get_batch_qty(stock_entry.items[0].batch_no, stock_entry.items[0].t_warehouse), 90
|
||||
)
|
||||
self.assertEqual(get_batch_qty(stock_entry.items[0].batch_no, stock_entry.items[0].t_warehouse), 90)
|
||||
|
||||
def test_delivery_note(self):
|
||||
"""Test automatic batch selection for outgoing items"""
|
||||
@@ -159,9 +157,7 @@ class TestBatch(FrappeTestCase):
|
||||
receipt = self.test_purchase_receipt()
|
||||
from erpnext.stock.doctype.batch.batch import split_batch
|
||||
|
||||
new_batch = split_batch(
|
||||
receipt.items[0].batch_no, "ITEM-BATCH-1", receipt.items[0].warehouse, 22
|
||||
)
|
||||
new_batch = split_batch(receipt.items[0].batch_no, "ITEM-BATCH-1", receipt.items[0].warehouse, 22)
|
||||
|
||||
self.assertEqual(get_batch_qty(receipt.items[0].batch_no, receipt.items[0].warehouse), 78)
|
||||
self.assertEqual(get_batch_qty(new_batch, receipt.items[0].warehouse), 22)
|
||||
@@ -359,9 +355,7 @@ class TestBatch(FrappeTestCase):
|
||||
self.make_batch_item(item_code)
|
||||
|
||||
def assertValuation(expected):
|
||||
actual = get_valuation_rate(
|
||||
item_code, warehouse, "voucher_type", "voucher_no", batch_no=batch_no
|
||||
)
|
||||
actual = get_valuation_rate(item_code, warehouse, "voucher_type", "voucher_no", batch_no=batch_no)
|
||||
self.assertAlmostEqual(actual, expected)
|
||||
|
||||
se = make_stock_entry(item_code=item_code, qty=100, rate=10, target=warehouse)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Bin', {
|
||||
refresh: function(frm) {
|
||||
|
||||
}
|
||||
frappe.ui.form.on("Bin", {
|
||||
refresh: function (frm) {},
|
||||
});
|
||||
|
||||
@@ -51,8 +51,7 @@
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Item",
|
||||
"read_only": 1,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0.00",
|
||||
|
||||
@@ -58,9 +58,7 @@ class Bin(Document):
|
||||
get_reserved_qty_for_sub_assembly,
|
||||
)
|
||||
|
||||
reserved_qty_for_production_plan = get_reserved_qty_for_sub_assembly(
|
||||
self.item_code, self.warehouse
|
||||
)
|
||||
reserved_qty_for_production_plan = get_reserved_qty_for_sub_assembly(self.item_code, self.warehouse)
|
||||
|
||||
if reserved_qty_for_production_plan is None and not self.reserved_qty_for_production_plan:
|
||||
return
|
||||
@@ -81,9 +79,7 @@ class Bin(Document):
|
||||
in open work orders"""
|
||||
from erpnext.manufacturing.doctype.work_order.work_order import get_reserved_qty_for_production
|
||||
|
||||
self.reserved_qty_for_production = get_reserved_qty_for_production(
|
||||
self.item_code, self.warehouse
|
||||
)
|
||||
self.reserved_qty_for_production = get_reserved_qty_for_production(self.item_code, self.warehouse)
|
||||
|
||||
self.db_set(
|
||||
"reserved_qty_for_production", flt(self.reserved_qty_for_production), update_modified=True
|
||||
@@ -131,9 +127,7 @@ class Bin(Document):
|
||||
se_item = frappe.qb.DocType("Stock Entry Detail")
|
||||
|
||||
if frappe.db.field_exists("Stock Entry", "is_return"):
|
||||
qty_field = (
|
||||
Case().when(se.is_return == 1, se_item.transfer_qty * -1).else_(se_item.transfer_qty)
|
||||
)
|
||||
qty_field = Case().when(se.is_return == 1, se_item.transfer_qty * -1).else_(se_item.transfer_qty)
|
||||
else:
|
||||
qty_field = se_item.transfer_qty
|
||||
|
||||
|
||||
@@ -31,4 +31,4 @@ class TestBin(FrappeTestCase):
|
||||
def test_index_exists(self):
|
||||
indexes = frappe.db.sql("show index from tabBin where Non_unique = 0", as_dict=1)
|
||||
if not any(index.get("Key_name") == "unique_item_warehouse" for index in indexes):
|
||||
self.fail(f"Expected unique index on item-warehouse")
|
||||
self.fail("Expected unique index on item-warehouse")
|
||||
|
||||
@@ -16,9 +16,9 @@ frappe.ui.form.on("Closing Stock Balance", {
|
||||
freeze: true,
|
||||
callback: () => {
|
||||
frm.reload_doc();
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -31,9 +31,9 @@ frappe.ui.form.on("Closing Stock Balance", {
|
||||
freeze: true,
|
||||
callback: () => {
|
||||
frm.reload_doc();
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -44,7 +44,7 @@ class ClosingStockBalance(Document):
|
||||
& (
|
||||
(table.from_date.between(self.from_date, self.to_date))
|
||||
| (table.to_date.between(self.from_date, self.to_date))
|
||||
| (table.from_date >= self.from_date and table.to_date <= self.to_date)
|
||||
| ((table.from_date >= self.from_date) & (table.to_date >= self.to_date))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -126,8 +126,6 @@ def prepare_closing_stock_balance(name):
|
||||
try:
|
||||
doc.create_closing_stock_balance_entries()
|
||||
doc.db_set("status", "Completed")
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
doc.db_set("status", "Failed")
|
||||
traceback = frappe.get_traceback()
|
||||
|
||||
frappe.log_error("Closing Stock Balance Failed", traceback, doc.doctype, doc.name)
|
||||
doc.log_error(title="Closing Stock Balance Failed")
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Customs Tariff Number', {
|
||||
refresh: function(frm) {
|
||||
|
||||
}
|
||||
frappe.ui.form.on("Customs Tariff Number", {
|
||||
refresh: function (frm) {},
|
||||
});
|
||||
|
||||
@@ -680,7 +680,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "Long Text",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Taxes and Charges Calculation",
|
||||
"no_copy": 1,
|
||||
"oldfieldtype": "HTML",
|
||||
@@ -1401,7 +1401,7 @@
|
||||
"idx": 146,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-18 17:19:39.368239",
|
||||
"modified": "2024-03-20 16:05:02.854990",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note",
|
||||
|
||||
@@ -10,7 +10,7 @@ from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.utils import cint, flt
|
||||
|
||||
from erpnext.controllers.accounts_controller import get_taxes_and_charges
|
||||
from erpnext.controllers.accounts_controller import get_taxes_and_charges, merge_taxes
|
||||
from erpnext.controllers.selling_controller import SellingController
|
||||
from erpnext.stock.doctype.batch.batch import set_batch_nos
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no
|
||||
@@ -20,7 +20,7 @@ form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
|
||||
|
||||
class DeliveryNote(SellingController):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DeliveryNote, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.status_updater = [
|
||||
{
|
||||
"source_dt": "Delivery Note Item",
|
||||
@@ -108,7 +108,7 @@ class DeliveryNote(SellingController):
|
||||
for f in fieldname:
|
||||
toggle_print_hide(self.meta if key == "parent" else item_meta, f)
|
||||
|
||||
super(DeliveryNote, self).before_print(settings)
|
||||
super().before_print(settings)
|
||||
|
||||
def set_actual_qty(self):
|
||||
for d in self.get("items"):
|
||||
@@ -129,7 +129,8 @@ class DeliveryNote(SellingController):
|
||||
|
||||
def validate(self):
|
||||
self.validate_posting_time()
|
||||
super(DeliveryNote, self).validate()
|
||||
super().validate()
|
||||
self.validate_references()
|
||||
self.set_status()
|
||||
self.so_required()
|
||||
self.validate_proj_cust()
|
||||
@@ -158,11 +159,16 @@ class DeliveryNote(SellingController):
|
||||
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
|
||||
def validate_with_previous_doc(self):
|
||||
super(DeliveryNote, self).validate_with_previous_doc(
|
||||
super().validate_with_previous_doc(
|
||||
{
|
||||
"Sales Order": {
|
||||
"ref_dn_field": "against_sales_order",
|
||||
"compare_fields": [["customer", "="], ["company", "="], ["project", "="], ["currency", "="]],
|
||||
"compare_fields": [
|
||||
["customer", "="],
|
||||
["company", "="],
|
||||
["project", "="],
|
||||
["currency", "="],
|
||||
],
|
||||
},
|
||||
"Sales Order Item": {
|
||||
"ref_dn_field": "so_detail",
|
||||
@@ -172,7 +178,12 @@ class DeliveryNote(SellingController):
|
||||
},
|
||||
"Sales Invoice": {
|
||||
"ref_dn_field": "against_sales_invoice",
|
||||
"compare_fields": [["customer", "="], ["company", "="], ["project", "="], ["currency", "="]],
|
||||
"compare_fields": [
|
||||
["customer", "="],
|
||||
["company", "="],
|
||||
["project", "="],
|
||||
["currency", "="],
|
||||
],
|
||||
},
|
||||
"Sales Invoice Item": {
|
||||
"ref_dn_field": "si_detail",
|
||||
@@ -195,6 +206,58 @@ class DeliveryNote(SellingController):
|
||||
]
|
||||
)
|
||||
|
||||
def validate_references(self):
|
||||
self.validate_sales_order_references()
|
||||
self.validate_sales_invoice_references()
|
||||
|
||||
def validate_sales_order_references(self):
|
||||
err_msg = ""
|
||||
for item in self.items:
|
||||
if (item.against_sales_order and not item.so_detail) or (
|
||||
not item.against_sales_order and item.so_detail
|
||||
):
|
||||
if not item.against_sales_order:
|
||||
err_msg += (
|
||||
_("'Sales Order' reference ({1}) is missing in row {0}").format(
|
||||
frappe.bold(item.idx), frappe.bold("against_sales_order")
|
||||
)
|
||||
+ "<br>"
|
||||
)
|
||||
else:
|
||||
err_msg += (
|
||||
_("'Sales Order Item' reference ({1}) is missing in row {0}").format(
|
||||
frappe.bold(item.idx), frappe.bold("so_detail")
|
||||
)
|
||||
+ "<br>"
|
||||
)
|
||||
|
||||
if err_msg:
|
||||
frappe.throw(err_msg, title=_("References to Sales Orders are Incomplete"))
|
||||
|
||||
def validate_sales_invoice_references(self):
|
||||
err_msg = ""
|
||||
for item in self.items:
|
||||
if (item.against_sales_invoice and not item.si_detail) or (
|
||||
not item.against_sales_invoice and item.si_detail
|
||||
):
|
||||
if not item.against_sales_invoice:
|
||||
err_msg += (
|
||||
_("'Sales Invoice' reference ({1}) is missing in row {0}").format(
|
||||
frappe.bold(item.idx), frappe.bold("against_sales_invoice")
|
||||
)
|
||||
+ "<br>"
|
||||
)
|
||||
else:
|
||||
err_msg += (
|
||||
_("'Sales Invoice Item' reference ({1}) is missing in row {0}").format(
|
||||
frappe.bold(item.idx), frappe.bold("si_detail")
|
||||
)
|
||||
+ "<br>"
|
||||
)
|
||||
|
||||
if err_msg:
|
||||
frappe.throw(err_msg, title=_("References to Sales Invoices are Incomplete"))
|
||||
|
||||
def validate_proj_cust(self):
|
||||
"""check for does customer belong to same project as entered.."""
|
||||
if self.project and self.customer:
|
||||
@@ -210,7 +273,7 @@ class DeliveryNote(SellingController):
|
||||
)
|
||||
|
||||
def validate_warehouse(self):
|
||||
super(DeliveryNote, self).validate_warehouse()
|
||||
super().validate_warehouse()
|
||||
|
||||
for d in self.get_item_list():
|
||||
if not d["warehouse"] and frappe.get_cached_value("Item", d["item_code"], "is_stock_item") == 1:
|
||||
@@ -258,7 +321,7 @@ class DeliveryNote(SellingController):
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
def on_cancel(self):
|
||||
super(DeliveryNote, self).on_cancel()
|
||||
super().on_cancel()
|
||||
|
||||
self.check_sales_order_on_hold_or_close("against_sales_order")
|
||||
self.check_next_docstatus()
|
||||
@@ -570,7 +633,7 @@ def get_returned_qty_map(delivery_note):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_invoice(source_name, target_doc=None):
|
||||
def make_sales_invoice(source_name, target_doc=None, args=None):
|
||||
doc = frappe.get_doc("Delivery Note", source_name)
|
||||
|
||||
to_make_invoice_qty_map = {}
|
||||
@@ -584,6 +647,9 @@ def make_sales_invoice(source_name, target_doc=None):
|
||||
if len(target.get("items")) == 0:
|
||||
frappe.throw(_("All these items have already been Invoiced/Returned"))
|
||||
|
||||
if args and args.get("merge_taxes"):
|
||||
merge_taxes(source.get("taxes") or [], target)
|
||||
|
||||
target.run_method("calculate_taxes_and_totals")
|
||||
|
||||
# set company address
|
||||
@@ -648,7 +714,11 @@ def make_sales_invoice(source_name, target_doc=None):
|
||||
if not doc.get("is_return")
|
||||
else get_pending_qty(d) > 0,
|
||||
},
|
||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
|
||||
"Sales Taxes and Charges": {
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"add_if_empty": True,
|
||||
"ignore": args.get("merge_taxes") if args else 0,
|
||||
},
|
||||
"Sales Team": {
|
||||
"doctype": "Sales Team",
|
||||
"field_map": {"incentives": "incentives"},
|
||||
@@ -702,7 +772,7 @@ def make_delivery_trip(source_name, target_doc=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_installation_note(source_name, target_doc=None):
|
||||
def make_installation_note(source_name, target_doc=None, kwargs=None):
|
||||
def update_item(obj, target, source_parent):
|
||||
target.qty = flt(obj.qty) - flt(obj.installed_qty)
|
||||
target.serial_no = obj.serial_no
|
||||
@@ -791,7 +861,7 @@ def make_shipment(source_name, target_doc=None):
|
||||
"User", frappe.session.user, ["email", "full_name", "phone", "mobile_no"], as_dict=1
|
||||
)
|
||||
target.pickup_contact_email = user.email
|
||||
pickup_contact_display = "{}".format(user.full_name)
|
||||
pickup_contact_display = f"{user.full_name}"
|
||||
if user:
|
||||
if user.email:
|
||||
pickup_contact_display += "<br>" + user.email
|
||||
@@ -807,7 +877,7 @@ def make_shipment(source_name, target_doc=None):
|
||||
contact = frappe.db.get_value(
|
||||
"Contact", source.contact_person, ["email_id", "phone", "mobile_no"], as_dict=1
|
||||
)
|
||||
delivery_contact_display = "{}".format(source.contact_display)
|
||||
delivery_contact_display = f"{source.contact_display}"
|
||||
if contact:
|
||||
if contact.email_id:
|
||||
delivery_contact_display += "<br>" + contact.email_id
|
||||
@@ -912,6 +982,9 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
for tax in get_taxes_and_charges(master_doctype, target.get("taxes_and_charges")):
|
||||
target.append("taxes", tax)
|
||||
|
||||
if not target.get("items"):
|
||||
frappe.throw(_("All items have already been received"))
|
||||
|
||||
def update_details(source_doc, target_doc, source_parent):
|
||||
target_doc.inter_company_invoice_reference = source_doc.name
|
||||
if target_doc.doctype == "Purchase Receipt":
|
||||
@@ -967,6 +1040,10 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
shipping_address_name=target_doc.shipping_address_name,
|
||||
)
|
||||
|
||||
def update_item(source, target, source_parent):
|
||||
if source_parent.doctype == "Delivery Note" and source.received_qty:
|
||||
target.qty = flt(source.qty) + flt(source.returned_qty) - flt(source.received_qty)
|
||||
|
||||
doclist = get_mapped_doc(
|
||||
doctype,
|
||||
source_name,
|
||||
@@ -976,8 +1053,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
"postprocess": update_details,
|
||||
"field_no_map": ["taxes_and_charges", "set_warehouse"],
|
||||
},
|
||||
doctype
|
||||
+ " Item": {
|
||||
doctype + " Item": {
|
||||
"doctype": target_doctype + " Item",
|
||||
"field_map": {
|
||||
source_document_warehouse_field: target_document_warehouse_field,
|
||||
@@ -990,6 +1066,8 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
"Material_request_item": "material_request_item",
|
||||
},
|
||||
"field_no_map": ["warehouse"],
|
||||
"condition": lambda item: item.received_qty < item.qty + item.returned_qty,
|
||||
"postprocess": update_item,
|
||||
},
|
||||
},
|
||||
target_doc,
|
||||
|
||||
@@ -8,6 +8,7 @@ def get_data():
|
||||
"Stock Entry": "delivery_note_no",
|
||||
"Quality Inspection": "reference_name",
|
||||
"Auto Repeat": "reference_document",
|
||||
"Purchase Receipt": "inter_company_reference",
|
||||
},
|
||||
"internal_links": {
|
||||
"Sales Order": ["items", "against_sales_order"],
|
||||
@@ -22,6 +23,9 @@ def get_data():
|
||||
{"label": _("Reference"), "items": ["Sales Order", "Shipment", "Quality Inspection"]},
|
||||
{"label": _("Returns"), "items": ["Stock Entry"]},
|
||||
{"label": _("Subscription"), "items": ["Auto Repeat"]},
|
||||
{"label": _("Internal Transfer"), "items": ["Material Request", "Purchase Order"]},
|
||||
{
|
||||
"label": _("Internal Transfer"),
|
||||
"items": ["Material Request", "Purchase Order", "Purchase Receipt"],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
frappe.listview_settings['Delivery Note'] = {
|
||||
add_fields: ["customer", "customer_name", "base_grand_total", "per_installed", "per_billed",
|
||||
"transporter_name", "grand_total", "is_return", "status", "currency"],
|
||||
get_indicator: function(doc) {
|
||||
if(cint(doc.is_return)==1) {
|
||||
frappe.listview_settings["Delivery Note"] = {
|
||||
add_fields: [
|
||||
"customer",
|
||||
"customer_name",
|
||||
"base_grand_total",
|
||||
"per_installed",
|
||||
"per_billed",
|
||||
"transporter_name",
|
||||
"grand_total",
|
||||
"is_return",
|
||||
"status",
|
||||
"currency",
|
||||
],
|
||||
get_indicator: function (doc) {
|
||||
if (cint(doc.is_return) == 1) {
|
||||
return [__("Return"), "gray", "is_return,=,Yes"];
|
||||
} else if (doc.status === "Closed") {
|
||||
return [__("Closed"), "green", "status,=,Closed"];
|
||||
@@ -24,46 +34,45 @@ frappe.listview_settings['Delivery Note'] = {
|
||||
if (!doc.docstatus) {
|
||||
frappe.throw(__("Cannot create a Delivery Trip from Draft documents."));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
frappe.new_doc("Delivery Trip")
|
||||
.then(() => {
|
||||
// Empty out the child table before inserting new ones
|
||||
cur_frm.set_value("delivery_stops", []);
|
||||
frappe.new_doc("Delivery Trip").then(() => {
|
||||
// Empty out the child table before inserting new ones
|
||||
cur_frm.set_value("delivery_stops", []);
|
||||
|
||||
// We don't want to use `map_current_doc` since it brings up
|
||||
// the dialog to select more items. We just want the mapper
|
||||
// function to be called.
|
||||
frappe.call({
|
||||
type: "POST",
|
||||
method: "frappe.model.mapper.map_docs",
|
||||
args: {
|
||||
"method": "erpnext.stock.doctype.delivery_note.delivery_note.make_delivery_trip",
|
||||
"source_names": docnames,
|
||||
"target_doc": cur_frm.doc
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
frappe.model.sync(r.message);
|
||||
cur_frm.dirty();
|
||||
cur_frm.refresh();
|
||||
}
|
||||
// We don't want to use `map_current_doc` since it brings up
|
||||
// the dialog to select more items. We just want the mapper
|
||||
// function to be called.
|
||||
frappe.call({
|
||||
type: "POST",
|
||||
method: "frappe.model.mapper.map_docs",
|
||||
args: {
|
||||
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_delivery_trip",
|
||||
source_names: docnames,
|
||||
target_doc: cur_frm.doc,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
frappe.model.sync(r.message);
|
||||
cur_frm.dirty();
|
||||
cur_frm.refresh();
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// doclist.page.add_actions_menu_item(__('Create Delivery Trip'), action, false);
|
||||
|
||||
doclist.page.add_action_item(__('Create Delivery Trip'), action);
|
||||
doclist.page.add_action_item(__("Create Delivery Trip"), action);
|
||||
|
||||
doclist.page.add_action_item(__("Sales Invoice"), ()=>{
|
||||
doclist.page.add_action_item(__("Sales Invoice"), () => {
|
||||
erpnext.bulk_transaction_processing.create(doclist, "Delivery Note", "Sales Invoice");
|
||||
});
|
||||
|
||||
doclist.page.add_action_item(__("Packaging Slip From Delivery Note"), ()=>{
|
||||
doclist.page.add_action_item(__("Packaging Slip From Delivery Note"), () => {
|
||||
erpnext.bulk_transaction_processing.create(doclist, "Delivery Note", "Packing Slip");
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
import click
|
||||
import frappe
|
||||
|
||||
UNUSED_INDEXES = [
|
||||
("Delivery Note", ["customer", "is_return", "return_against"]),
|
||||
("Sales Invoice", ["customer", "is_return", "return_against"]),
|
||||
("Purchase Invoice", ["supplier", "is_return", "return_against"]),
|
||||
("Purchase Receipt", ["supplier", "is_return", "return_against"]),
|
||||
]
|
||||
|
||||
|
||||
def execute():
|
||||
"""Drop unused return_against index"""
|
||||
for doctype, index_fields in UNUSED_INDEXES:
|
||||
table = f"tab{doctype}"
|
||||
index_name = frappe.db.get_index_name(index_fields)
|
||||
drop_index_if_exists(table, index_name)
|
||||
|
||||
|
||||
def drop_index_if_exists(table: str, index: str):
|
||||
if not frappe.db.has_index(table, index):
|
||||
return
|
||||
|
||||
try:
|
||||
frappe.db.sql_ddl(
|
||||
"ALTER TABLE `tabDelivery Note` DROP INDEX `customer_is_return_return_against_index`"
|
||||
)
|
||||
frappe.db.sql_ddl(
|
||||
"ALTER TABLE `tabPurchase Receipt` DROP INDEX `supplier_is_return_return_against_index`"
|
||||
)
|
||||
frappe.db.sql_ddl(f"ALTER TABLE `{table}` DROP INDEX `{index}`")
|
||||
click.echo(f"✓ dropped {index} index from {table}")
|
||||
except Exception:
|
||||
frappe.log_error("Failed to drop unused index")
|
||||
frappe.log_error("Failed to drop index")
|
||||
|
||||
@@ -54,7 +54,7 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
self.assertRaises(frappe.ValidationError, frappe.get_doc(si).insert)
|
||||
|
||||
def test_delivery_note_no_gl_entry(self):
|
||||
company = frappe.db.get_value("Warehouse", "_Test Warehouse - _TC", "company")
|
||||
frappe.db.get_value("Warehouse", "_Test Warehouse - _TC", "company")
|
||||
make_stock_entry(target="_Test Warehouse - _TC", qty=5, basic_rate=100)
|
||||
|
||||
stock_queue = json.loads(
|
||||
@@ -71,16 +71,14 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
|
||||
dn = create_delivery_note()
|
||||
|
||||
sle = frappe.get_doc(
|
||||
"Stock Ledger Entry", {"voucher_type": "Delivery Note", "voucher_no": dn.name}
|
||||
)
|
||||
sle = frappe.get_doc("Stock Ledger Entry", {"voucher_type": "Delivery Note", "voucher_no": dn.name})
|
||||
|
||||
self.assertEqual(sle.stock_value_difference, flt(-1 * stock_queue[0][1], 2))
|
||||
|
||||
self.assertFalse(get_gl_entries("Delivery Note", dn.name))
|
||||
|
||||
def test_delivery_note_gl_entry_packing_item(self):
|
||||
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
|
||||
frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
|
||||
|
||||
make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=10, basic_rate=100)
|
||||
make_stock_entry(
|
||||
@@ -127,7 +125,7 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
stock_in_hand_account: [0.0, stock_value_diff],
|
||||
"Cost of Goods Sold - TCP1": [stock_value_diff, 0.0],
|
||||
}
|
||||
for i, gle in enumerate(gl_entries):
|
||||
for _i, gle in enumerate(gl_entries):
|
||||
self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account))
|
||||
|
||||
# check stock in hand balance
|
||||
@@ -159,9 +157,7 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
serial_no = get_serial_nos(se.get("items")[0].serial_no)
|
||||
serial_no = "\n".join(serial_no)
|
||||
|
||||
dn = create_delivery_note(
|
||||
item_code="_Test Serialized Item With Series", qty=2, serial_no=serial_no
|
||||
)
|
||||
dn = create_delivery_note(item_code="_Test Serialized Item With Series", qty=2, serial_no=serial_no)
|
||||
|
||||
si = make_sales_invoice(dn.name)
|
||||
si.items[0].qty = 1
|
||||
@@ -694,7 +690,7 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
"Stock In Hand - TCP1": [0.0, stock_value_difference],
|
||||
target_warehouse: [stock_value_difference, 0.0],
|
||||
}
|
||||
for i, gle in enumerate(gl_entries):
|
||||
for _i, gle in enumerate(gl_entries):
|
||||
self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account))
|
||||
|
||||
# tear down
|
||||
@@ -723,6 +719,15 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
dn.cancel()
|
||||
self.assertEqual(dn.status, "Cancelled")
|
||||
|
||||
def test_sales_order_reference_validation(self):
|
||||
so = make_sales_order(po_no="12345")
|
||||
dn = create_dn_against_so(so.name, delivered_qty=2, do_not_submit=True)
|
||||
dn.items[0].against_sales_order = None
|
||||
self.assertRaises(frappe.ValidationError, dn.save)
|
||||
dn.reload()
|
||||
dn.items[0].so_detail = None
|
||||
self.assertRaises(frappe.ValidationError, dn.save)
|
||||
|
||||
def test_dn_billing_status_case1(self):
|
||||
# SO -> DN -> SI
|
||||
so = make_sales_order(po_no="12345")
|
||||
@@ -893,7 +898,7 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
"Cost of Goods Sold - TCP1": {"cost_center": cost_center},
|
||||
stock_in_hand_account: {"cost_center": cost_center},
|
||||
}
|
||||
for i, gle in enumerate(gl_entries):
|
||||
for _i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
|
||||
|
||||
def test_delivery_note_cost_center_with_balance_sheet_account(self):
|
||||
@@ -922,7 +927,7 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
"Cost of Goods Sold - TCP1": {"cost_center": cost_center},
|
||||
stock_in_hand_account: {"cost_center": cost_center},
|
||||
}
|
||||
for i, gle in enumerate(gl_entries):
|
||||
for _i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
|
||||
|
||||
def test_make_sales_invoice_from_dn_for_returned_qty(self):
|
||||
@@ -988,9 +993,7 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
},
|
||||
)
|
||||
make_product_bundle(parent=batched_bundle.name, items=[batched_item.name])
|
||||
make_stock_entry(
|
||||
item_code=batched_item.name, target="_Test Warehouse - _TC", qty=10, basic_rate=42
|
||||
)
|
||||
make_stock_entry(item_code=batched_item.name, target="_Test Warehouse - _TC", qty=10, basic_rate=42)
|
||||
|
||||
try:
|
||||
dn = create_delivery_note(item_code=batched_bundle.name, qty=1)
|
||||
@@ -999,9 +1002,7 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
self.fail("Batch numbers not getting added to bundled items in DN.")
|
||||
raise e
|
||||
|
||||
self.assertTrue(
|
||||
"TESTBATCH" in dn.packed_items[0].batch_no, "Batch number not added in packed item"
|
||||
)
|
||||
self.assertTrue("TESTBATCH" in dn.packed_items[0].batch_no, "Batch number not added in packed item")
|
||||
|
||||
def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
|
||||
@@ -1176,9 +1177,7 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
warehouse=warehouse,
|
||||
target_warehouse=target,
|
||||
)
|
||||
self.assertFalse(
|
||||
frappe.db.exists("GL Entry", {"voucher_no": dn.name, "voucher_type": dn.doctype})
|
||||
)
|
||||
self.assertFalse(frappe.db.exists("GL Entry", {"voucher_no": dn.name, "voucher_type": dn.doctype}))
|
||||
|
||||
def test_batch_expiry_for_delivery_note(self):
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
@@ -1253,11 +1252,9 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
).name
|
||||
|
||||
# Step - 2: Inward Stock
|
||||
se1 = make_stock_entry(item_code=batch_item, target="_Test Warehouse - _TC", qty=3)
|
||||
make_stock_entry(item_code=batch_item, target="_Test Warehouse - _TC", qty=3)
|
||||
serial_nos = (
|
||||
make_stock_entry(item_code=serial_item, target="_Test Warehouse - _TC", qty=3)
|
||||
.items[0]
|
||||
.serial_no
|
||||
make_stock_entry(item_code=serial_item, target="_Test Warehouse - _TC", qty=3).items[0].serial_no
|
||||
)
|
||||
|
||||
# Step - 3: Create a Product Bundle
|
||||
@@ -1332,6 +1329,126 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
dn.reload()
|
||||
self.assertFalse(dn.items[0].target_warehouse)
|
||||
|
||||
def test_sales_return_valuation_for_moving_average(self):
|
||||
item_code = make_item(
|
||||
"_Test Item Sales Return with MA", {"is_stock_item": 1, "valuation_method": "Moving Average"}
|
||||
).name
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item_code,
|
||||
target="_Test Warehouse - _TC",
|
||||
qty=5,
|
||||
basic_rate=100.0,
|
||||
posting_date=add_days(nowdate(), -5),
|
||||
)
|
||||
dn = create_delivery_note(item_code=item_code, qty=5, rate=500, posting_date=add_days(nowdate(), -4))
|
||||
self.assertEqual(dn.items[0].incoming_rate, 100.0)
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item_code,
|
||||
target="_Test Warehouse - _TC",
|
||||
qty=5,
|
||||
basic_rate=200.0,
|
||||
posting_date=add_days(nowdate(), -3),
|
||||
)
|
||||
make_stock_entry(
|
||||
item_code=item_code,
|
||||
target="_Test Warehouse - _TC",
|
||||
qty=5,
|
||||
basic_rate=300.0,
|
||||
posting_date=add_days(nowdate(), -2),
|
||||
)
|
||||
|
||||
dn1 = create_delivery_note(
|
||||
is_return=1,
|
||||
item_code=item_code,
|
||||
return_against=dn.name,
|
||||
qty=-5,
|
||||
rate=500,
|
||||
company=dn.company,
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
cost_center="Main - _TC",
|
||||
do_not_submit=1,
|
||||
posting_date=add_days(nowdate(), -1),
|
||||
)
|
||||
|
||||
# (300 * 5) + (200 * 5) = 2500
|
||||
# 2500 / 10 = 250
|
||||
|
||||
self.assertAlmostEqual(dn1.items[0].incoming_rate, 250.0)
|
||||
|
||||
def test_sales_return_valuation_for_moving_average_case2(self):
|
||||
# Make DN return
|
||||
# Make Bakcdated Purchase Receipt and check DN return valuation rate
|
||||
# The rate should be recalculate based on the backdated purchase receipt
|
||||
frappe.flags.print_debug_messages = False
|
||||
item_code = make_item(
|
||||
"_Test Item Sales Return with MA Case2",
|
||||
{"is_stock_item": 1, "valuation_method": "Moving Average", "stock_uom": "Nos"},
|
||||
).name
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item_code,
|
||||
target="_Test Warehouse - _TC",
|
||||
qty=5,
|
||||
basic_rate=100.0,
|
||||
posting_date=add_days(nowdate(), -5),
|
||||
)
|
||||
|
||||
dn = create_delivery_note(
|
||||
item_code=item_code,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
qty=5,
|
||||
rate=500,
|
||||
posting_date=add_days(nowdate(), -4),
|
||||
)
|
||||
|
||||
returned_dn = create_delivery_note(
|
||||
is_return=1,
|
||||
item_code=item_code,
|
||||
return_against=dn.name,
|
||||
qty=-5,
|
||||
rate=500,
|
||||
company=dn.company,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
cost_center="Main - _TC",
|
||||
posting_date=add_days(nowdate(), -1),
|
||||
)
|
||||
|
||||
self.assertAlmostEqual(returned_dn.items[0].incoming_rate, 100.0)
|
||||
|
||||
# Make backdated purchase receipt
|
||||
make_stock_entry(
|
||||
item_code=item_code,
|
||||
target="_Test Warehouse - _TC",
|
||||
qty=5,
|
||||
basic_rate=200.0,
|
||||
posting_date=add_days(nowdate(), -3),
|
||||
)
|
||||
|
||||
returned_dn.reload()
|
||||
self.assertAlmostEqual(returned_dn.items[0].incoming_rate, 200.0)
|
||||
|
||||
def test_internal_transfer_for_non_stock_item(self):
|
||||
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
|
||||
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
|
||||
|
||||
item = make_item(properties={"is_stock_item": 0}).name
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
target = "Stores - _TC"
|
||||
company = "_Test Company"
|
||||
customer = create_internal_customer(represents_company=company)
|
||||
rate = 100
|
||||
|
||||
so = make_sales_order(item_code=item, qty=1, rate=rate, customer=customer, warehouse=warehouse)
|
||||
dn = make_delivery_note(so.name)
|
||||
dn.items[0].target_warehouse = target
|
||||
dn.save().submit()
|
||||
|
||||
self.assertEqual(so.items[0].rate, rate)
|
||||
self.assertEqual(dn.items[0].rate, so.items[0].rate)
|
||||
|
||||
|
||||
def create_delivery_note(**args):
|
||||
dn = frappe.new_doc("Delivery Note")
|
||||
|
||||
@@ -809,7 +809,8 @@
|
||||
"label": "Purchase Order",
|
||||
"options": "Purchase Order",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_82",
|
||||
@@ -870,7 +871,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-14 18:37:38.638144",
|
||||
"modified": "2024-03-21 18:15:07.603672",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note Item",
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Delivery Settings', {
|
||||
refresh: function(frm) {
|
||||
|
||||
}
|
||||
frappe.ui.form.on("Delivery Settings", {
|
||||
refresh: function (frm) {},
|
||||
});
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Delivery Trip', {
|
||||
frappe.ui.form.on("Delivery Trip", {
|
||||
setup: function (frm) {
|
||||
frm.set_indicator_formatter('customer', (stop) => (stop.visited) ? "green" : "orange");
|
||||
frm.set_indicator_formatter("customer", (stop) => (stop.visited ? "green" : "orange"));
|
||||
|
||||
frm.set_query("driver", function () {
|
||||
return {
|
||||
filters: {
|
||||
"status": "Active"
|
||||
}
|
||||
status: "Active",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -17,58 +17,71 @@ frappe.ui.form.on('Delivery Trip', {
|
||||
var row = locals[cdt][cdn];
|
||||
if (row.customer) {
|
||||
return {
|
||||
query: 'frappe.contacts.doctype.address.address.address_query',
|
||||
query: "frappe.contacts.doctype.address.address.address_query",
|
||||
filters: {
|
||||
link_doctype: "Customer",
|
||||
link_name: row.customer
|
||||
}
|
||||
link_name: row.customer,
|
||||
},
|
||||
};
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
frm.set_query("contact", "delivery_stops", function (doc, cdt, cdn) {
|
||||
var row = locals[cdt][cdn];
|
||||
if (row.customer) {
|
||||
return {
|
||||
query: 'frappe.contacts.doctype.contact.contact.contact_query',
|
||||
query: "frappe.contacts.doctype.contact.contact.contact_query",
|
||||
filters: {
|
||||
link_doctype: "Customer",
|
||||
link_name: row.customer
|
||||
}
|
||||
link_name: row.customer,
|
||||
},
|
||||
};
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.docstatus == 1 && frm.doc.delivery_stops.length > 0) {
|
||||
frm.add_custom_button(__("Notify Customers via Email"), function () {
|
||||
frm.trigger('notify_customers');
|
||||
frm.trigger("notify_customers");
|
||||
});
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus === 0) {
|
||||
frm.add_custom_button(__('Delivery Note'), () => {
|
||||
erpnext.utils.map_current_doc({
|
||||
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_delivery_trip",
|
||||
source_doctype: "Delivery Note",
|
||||
target: frm,
|
||||
date_field: "posting_date",
|
||||
setters: {
|
||||
company: frm.doc.company,
|
||||
},
|
||||
get_query_filters: {
|
||||
docstatus: 1,
|
||||
company: frm.doc.company,
|
||||
}
|
||||
})
|
||||
}, __("Get stops from"));
|
||||
}
|
||||
frm.add_custom_button(__("Delivery Notes"), function () {
|
||||
frappe.set_route("List", "Delivery Note",
|
||||
{'name': ["in", frm.doc.delivery_stops.map((stop) => {return stop.delivery_note;})]}
|
||||
frm.add_custom_button(
|
||||
__("Delivery Note"),
|
||||
() => {
|
||||
erpnext.utils.map_current_doc({
|
||||
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_delivery_trip",
|
||||
source_doctype: "Delivery Note",
|
||||
target: frm,
|
||||
date_field: "posting_date",
|
||||
setters: {
|
||||
company: frm.doc.company,
|
||||
},
|
||||
get_query_filters: {
|
||||
docstatus: 1,
|
||||
company: frm.doc.company,
|
||||
},
|
||||
});
|
||||
},
|
||||
__("Get stops from")
|
||||
);
|
||||
}, __("View"));
|
||||
}
|
||||
frm.add_custom_button(
|
||||
__("Delivery Notes"),
|
||||
function () {
|
||||
frappe.set_route("List", "Delivery Note", {
|
||||
name: [
|
||||
"in",
|
||||
frm.doc.delivery_stops.map((stop) => {
|
||||
return stop.delivery_note;
|
||||
}),
|
||||
],
|
||||
});
|
||||
},
|
||||
__("View")
|
||||
);
|
||||
},
|
||||
|
||||
calculate_arrival_time: function (frm) {
|
||||
@@ -77,13 +90,17 @@ frappe.ui.form.on('Delivery Trip', {
|
||||
}
|
||||
frappe.show_alert({
|
||||
message: "Calculating Arrival Times",
|
||||
indicator: 'orange'
|
||||
});
|
||||
frm.call("process_route", {
|
||||
optimize: false,
|
||||
}, () => {
|
||||
frm.reload_doc();
|
||||
indicator: "orange",
|
||||
});
|
||||
frm.call(
|
||||
"process_route",
|
||||
{
|
||||
optimize: false,
|
||||
},
|
||||
() => {
|
||||
frm.reload_doc();
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
driver: function (frm) {
|
||||
@@ -91,13 +108,13 @@ frappe.ui.form.on('Delivery Trip', {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.delivery_trip.delivery_trip.get_driver_email",
|
||||
args: {
|
||||
driver: frm.doc.driver
|
||||
driver: frm.doc.driver,
|
||||
},
|
||||
callback: (data) => {
|
||||
frm.set_value("driver_email", data.message.email);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
optimize_route: function (frm) {
|
||||
@@ -106,23 +123,27 @@ frappe.ui.form.on('Delivery Trip', {
|
||||
}
|
||||
frappe.show_alert({
|
||||
message: "Optimizing Route",
|
||||
indicator: 'orange'
|
||||
});
|
||||
frm.call("process_route", {
|
||||
optimize: true,
|
||||
}, () => {
|
||||
frm.reload_doc();
|
||||
indicator: "orange",
|
||||
});
|
||||
frm.call(
|
||||
"process_route",
|
||||
{
|
||||
optimize: true,
|
||||
},
|
||||
() => {
|
||||
frm.reload_doc();
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
notify_customers: function (frm) {
|
||||
$.each(frm.doc.delivery_stops || [], function (i, delivery_stop) {
|
||||
if (!delivery_stop.delivery_note) {
|
||||
frappe.msgprint({
|
||||
"message": __("No Delivery Note selected for Customer {}", [delivery_stop.customer]),
|
||||
"title": __("Warning"),
|
||||
"indicator": "orange",
|
||||
"alert": 1
|
||||
message: __("No Delivery Note selected for Customer {}", [delivery_stop.customer]),
|
||||
title: __("Warning"),
|
||||
indicator: "orange",
|
||||
alert: 1,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -135,48 +156,45 @@ frappe.ui.form.on('Delivery Trip', {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.delivery_trip.delivery_trip.notify_customers",
|
||||
args: {
|
||||
"delivery_trip": frm.doc.name
|
||||
delivery_trip: frm.doc.name,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
frm.doc.email_notification_sent = true;
|
||||
frm.refresh_field('email_notification_sent');
|
||||
frm.refresh_field("email_notification_sent");
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Delivery Stop', {
|
||||
frappe.ui.form.on("Delivery Stop", {
|
||||
customer: function (frm, cdt, cdn) {
|
||||
var row = locals[cdt][cdn];
|
||||
if (row.customer) {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.delivery_trip.delivery_trip.get_contact_and_address",
|
||||
args: { "name": row.customer },
|
||||
args: { name: row.customer },
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
if (r.message["shipping_address"]) {
|
||||
frappe.model.set_value(cdt, cdn, "address", r.message["shipping_address"].parent);
|
||||
}
|
||||
else {
|
||||
frappe.model.set_value(cdt, cdn, "address", '');
|
||||
} else {
|
||||
frappe.model.set_value(cdt, cdn, "address", "");
|
||||
}
|
||||
if (r.message["contact_person"]) {
|
||||
frappe.model.set_value(cdt, cdn, "contact", r.message["contact_person"].parent);
|
||||
} else {
|
||||
frappe.model.set_value(cdt, cdn, "contact", "");
|
||||
}
|
||||
else {
|
||||
frappe.model.set_value(cdt, cdn, "contact", '');
|
||||
}
|
||||
} else {
|
||||
frappe.model.set_value(cdt, cdn, "address", "");
|
||||
frappe.model.set_value(cdt, cdn, "contact", "");
|
||||
}
|
||||
else {
|
||||
frappe.model.set_value(cdt, cdn, "address", '');
|
||||
frappe.model.set_value(cdt, cdn, "contact", '');
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -186,12 +204,12 @@ frappe.ui.form.on('Delivery Stop', {
|
||||
if (row.address) {
|
||||
frappe.call({
|
||||
method: "frappe.contacts.doctype.address.address.get_address_display",
|
||||
args: { "address_dict": row.address },
|
||||
args: { address_dict: row.address },
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
frappe.model.set_value(cdt, cdn, "customer_address", r.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
frappe.model.set_value(cdt, cdn, "customer_address", "");
|
||||
@@ -203,15 +221,15 @@ frappe.ui.form.on('Delivery Stop', {
|
||||
if (row.contact) {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.delivery_trip.delivery_trip.get_contact_display",
|
||||
args: { "contact": row.contact },
|
||||
args: { contact: row.contact },
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
frappe.model.set_value(cdt, cdn, "customer_contact", r.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
frappe.model.set_value(cdt, cdn, "customer_contact", "");
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ from frappe.utils import cint, get_datetime, get_link_to_form
|
||||
|
||||
class DeliveryTrip(Document):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DeliveryTrip, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Google Maps returns distances in meters by default
|
||||
self.default_distance_uom = (
|
||||
@@ -67,9 +67,7 @@ class DeliveryTrip(Document):
|
||||
delete (bool, optional): Defaults to `False`. `True` if driver details need to be emptied, else `False`.
|
||||
"""
|
||||
|
||||
delivery_notes = list(
|
||||
set(stop.delivery_note for stop in self.delivery_stops if stop.delivery_note)
|
||||
)
|
||||
delivery_notes = list(set(stop.delivery_note for stop in self.delivery_stops if stop.delivery_note))
|
||||
|
||||
update_fields = {
|
||||
"driver": self.driver,
|
||||
@@ -315,14 +313,11 @@ def get_contact_display(contact):
|
||||
"Contact", contact, ["first_name", "last_name", "phone", "mobile_no"], as_dict=1
|
||||
)
|
||||
|
||||
contact_info.html = (
|
||||
""" <b>%(first_name)s %(last_name)s</b> <br> %(phone)s <br> %(mobile_no)s"""
|
||||
% {
|
||||
"first_name": contact_info.first_name,
|
||||
"last_name": contact_info.last_name or "",
|
||||
"phone": contact_info.phone or "",
|
||||
"mobile_no": contact_info.mobile_no or "",
|
||||
}
|
||||
contact_info.html = """ <b>{first_name} {last_name}</b> <br> {phone} <br> {mobile_no}""".format(
|
||||
first_name=contact_info.first_name,
|
||||
last_name=contact_info.last_name or "",
|
||||
phone=contact_info.phone or "",
|
||||
mobile_no=contact_info.mobile_no or "",
|
||||
)
|
||||
|
||||
return contact_info.html
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
frappe.listview_settings['Delivery Trip'] = {
|
||||
frappe.listview_settings["Delivery Trip"] = {
|
||||
add_fields: ["status"],
|
||||
get_indicator: function (doc) {
|
||||
if (in_list(["Cancelled", "Draft"], doc.status)) {
|
||||
@@ -8,5 +8,5 @@ frappe.listview_settings['Delivery Trip'] = {
|
||||
} else if (doc.status === "Completed") {
|
||||
return [__(doc.status), "green", "status,=," + doc.status];
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
@@ -1,43 +1,59 @@
|
||||
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Inventory Dimension', {
|
||||
frappe.ui.form.on("Inventory Dimension", {
|
||||
setup(frm) {
|
||||
frm.trigger('set_query_on_fields');
|
||||
frm.trigger("set_query_on_fields");
|
||||
},
|
||||
|
||||
set_query_on_fields(frm) {
|
||||
frm.set_query('reference_document', () => {
|
||||
frm.set_query("reference_document", () => {
|
||||
let invalid_doctypes = frappe.model.core_doctypes_list;
|
||||
invalid_doctypes.push('Batch', 'Serial No', 'Warehouse', 'Item', 'Inventory Dimension',
|
||||
'Accounting Dimension', 'Accounting Dimension Filter');
|
||||
invalid_doctypes.push(
|
||||
"Batch",
|
||||
"Serial No",
|
||||
"Warehouse",
|
||||
"Item",
|
||||
"Inventory Dimension",
|
||||
"Accounting Dimension",
|
||||
"Accounting Dimension Filter"
|
||||
);
|
||||
|
||||
return {
|
||||
filters: {
|
||||
'istable': 0,
|
||||
'issingle': 0,
|
||||
'name': ['not in', invalid_doctypes]
|
||||
}
|
||||
istable: 0,
|
||||
issingle: 0,
|
||||
name: ["not in", invalid_doctypes],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('document_type', () => {
|
||||
frm.set_query("document_type", () => {
|
||||
return {
|
||||
query: 'erpnext.stock.doctype.inventory_dimension.inventory_dimension.get_inventory_documents',
|
||||
query: "erpnext.stock.doctype.inventory_dimension.inventory_dimension.get_inventory_documents",
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
onload(frm) {
|
||||
frm.trigger('render_traget_field');
|
||||
frm.trigger("render_traget_field");
|
||||
frm.trigger("set_parent_fields");
|
||||
},
|
||||
|
||||
refresh(frm) {
|
||||
if (frm.doc.__onload && frm.doc.__onload.has_stock_ledger
|
||||
&& frm.doc.__onload.has_stock_ledger.length) {
|
||||
let allow_to_edit_fields = ['disabled', 'fetch_from_parent',
|
||||
'type_of_transaction', 'condition', 'mandatory_depends_on', 'validate_negative_stock'];
|
||||
if (
|
||||
frm.doc.__onload &&
|
||||
frm.doc.__onload.has_stock_ledger &&
|
||||
frm.doc.__onload.has_stock_ledger.length
|
||||
) {
|
||||
let allow_to_edit_fields = [
|
||||
"disabled",
|
||||
"fetch_from_parent",
|
||||
"type_of_transaction",
|
||||
"condition",
|
||||
"mandatory_depends_on",
|
||||
"validate_negative_stock",
|
||||
];
|
||||
|
||||
frm.fields.forEach((field) => {
|
||||
if (!in_list(allow_to_edit_fields, field.df.fieldname)) {
|
||||
@@ -47,8 +63,8 @@ frappe.ui.form.on('Inventory Dimension', {
|
||||
}
|
||||
|
||||
if (!frm.is_new()) {
|
||||
frm.add_custom_button(__('Delete Dimension'), () => {
|
||||
frm.trigger('delete_dimension');
|
||||
frm.add_custom_button(__("Delete Dimension"), () => {
|
||||
frm.trigger("delete_dimension");
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -62,39 +78,38 @@ frappe.ui.form.on('Inventory Dimension', {
|
||||
frm.set_df_property("fetch_from_parent", "options", frm.doc.reference_document);
|
||||
} else if (frm.doc.document_type && frm.doc.istable) {
|
||||
frappe.call({
|
||||
method: 'erpnext.stock.doctype.inventory_dimension.inventory_dimension.get_parent_fields',
|
||||
method: "erpnext.stock.doctype.inventory_dimension.inventory_dimension.get_parent_fields",
|
||||
args: {
|
||||
child_doctype: frm.doc.document_type,
|
||||
dimension_name: frm.doc.reference_document
|
||||
dimension_name: frm.doc.reference_document,
|
||||
},
|
||||
callback: (r) => {
|
||||
if (r.message && r.message.length) {
|
||||
frm.set_df_property("fetch_from_parent", "options",
|
||||
[""].concat(r.message));
|
||||
frm.set_df_property("fetch_from_parent", "options", [""].concat(r.message));
|
||||
} else {
|
||||
frm.set_df_property("fetch_from_parent", "hidden", 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
delete_dimension(frm) {
|
||||
let msg = (`
|
||||
let msg = `
|
||||
Custom fields related to this dimension will be deleted on deletion of dimension.
|
||||
<br> Do you want to delete {0} dimension?
|
||||
`);
|
||||
`;
|
||||
|
||||
frappe.confirm(__(msg, [frm.doc.name.bold()]), () => {
|
||||
frappe.call({
|
||||
method: 'erpnext.stock.doctype.inventory_dimension.inventory_dimension.delete_dimension',
|
||||
method: "erpnext.stock.doctype.inventory_dimension.inventory_dimension.delete_dimension",
|
||||
args: {
|
||||
dimension: frm.doc.name
|
||||
dimension: frm.doc.name,
|
||||
},
|
||||
callback: function () {
|
||||
frappe.set_route("List", "Inventory Dimension");
|
||||
},
|
||||
callback: function() {
|
||||
frappe.set_route('List', 'Inventory Dimension');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -281,7 +281,7 @@ def get_evaluated_inventory_dimension(doc, sl_dict, parent_doc=None):
|
||||
dimensions = get_document_wise_inventory_dimensions(doc.doctype)
|
||||
filter_dimensions = []
|
||||
for row in dimensions:
|
||||
if row.type_of_transaction:
|
||||
if row.type_of_transaction and row.type_of_transaction != "Both":
|
||||
if (
|
||||
row.type_of_transaction == "Inward"
|
||||
if doc.docstatus == 1
|
||||
@@ -360,9 +360,7 @@ def delete_dimension(dimension):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_parent_fields(child_doctype, dimension_name):
|
||||
parent_doctypes = frappe.get_all(
|
||||
"DocField", fields=["parent"], filters={"options": child_doctype}
|
||||
)
|
||||
parent_doctypes = frappe.get_all("DocField", fields=["parent"], filters={"options": child_doctype})
|
||||
|
||||
fields = []
|
||||
|
||||
|
||||
@@ -210,9 +210,7 @@ class TestInventoryDimension(FrappeTestCase):
|
||||
)
|
||||
|
||||
self.assertFalse(
|
||||
frappe.db.get_value(
|
||||
"Custom Field", {"fieldname": "project", "dt": "Stock Ledger Entry"}, "name"
|
||||
)
|
||||
frappe.db.get_value("Custom Field", {"fieldname": "project", "dt": "Stock Ledger Entry"}, "name")
|
||||
)
|
||||
|
||||
def test_check_mandatory_dimensions(self):
|
||||
@@ -296,9 +294,7 @@ class TestInventoryDimension(FrappeTestCase):
|
||||
se_doc.save()
|
||||
se_doc.submit()
|
||||
|
||||
entries = get_voucher_sl_entries(
|
||||
se_doc.name, ["warehouse", "store", "incoming_rate", "actual_qty"]
|
||||
)
|
||||
entries = get_voucher_sl_entries(se_doc.name, ["warehouse", "store", "incoming_rate", "actual_qty"])
|
||||
|
||||
for entry in entries:
|
||||
self.assertEqual(entry.warehouse, warehouse)
|
||||
@@ -429,6 +425,14 @@ class TestInventoryDimension(FrappeTestCase):
|
||||
)
|
||||
|
||||
warehouse = create_warehouse("Negative Stock Warehouse")
|
||||
|
||||
doc = make_stock_entry(item_code=item_code, source=warehouse, qty=10, do_not_submit=True)
|
||||
doc.items[0].inv_site = "Site 1"
|
||||
self.assertRaises(frappe.ValidationError, doc.submit)
|
||||
doc.reload()
|
||||
if doc.docstatus == 1:
|
||||
doc.cancel()
|
||||
|
||||
doc = make_stock_entry(item_code=item_code, target=warehouse, qty=10, do_not_submit=True)
|
||||
|
||||
doc.items[0].to_inv_site = "Site 1"
|
||||
@@ -480,7 +484,14 @@ def create_store_dimension():
|
||||
"autoname": "field:store_name",
|
||||
"fields": [{"label": "Store Name", "fieldname": "store_name", "fieldtype": "Data"}],
|
||||
"permissions": [
|
||||
{"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
|
||||
{
|
||||
"role": "System Manager",
|
||||
"permlevel": 0,
|
||||
"read": 1,
|
||||
"write": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
}
|
||||
],
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
@@ -502,7 +513,14 @@ def prepare_test_data():
|
||||
"autoname": "field:shelf_name",
|
||||
"fields": [{"label": "Shelf Name", "fieldname": "shelf_name", "fieldtype": "Data"}],
|
||||
"permissions": [
|
||||
{"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
|
||||
{
|
||||
"role": "System Manager",
|
||||
"permlevel": 0,
|
||||
"read": 1,
|
||||
"write": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
}
|
||||
],
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
@@ -524,7 +542,14 @@ def prepare_test_data():
|
||||
"autoname": "field:rack_name",
|
||||
"fields": [{"label": "Rack Name", "fieldname": "rack_name", "fieldtype": "Data"}],
|
||||
"permissions": [
|
||||
{"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
|
||||
{
|
||||
"role": "System Manager",
|
||||
"permlevel": 0,
|
||||
"read": 1,
|
||||
"write": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
}
|
||||
],
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
@@ -546,7 +571,14 @@ def prepare_test_data():
|
||||
"autoname": "field:pallet_name",
|
||||
"fields": [{"label": "Pallet Name", "fieldname": "pallet_name", "fieldtype": "Data"}],
|
||||
"permissions": [
|
||||
{"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
|
||||
{
|
||||
"role": "System Manager",
|
||||
"permlevel": 0,
|
||||
"read": 1,
|
||||
"write": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
}
|
||||
],
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
@@ -562,7 +594,14 @@ def prepare_test_data():
|
||||
"autoname": "field:site_name",
|
||||
"fields": [{"label": "Site Name", "fieldname": "site_name", "fieldtype": "Data"}],
|
||||
"permissions": [
|
||||
{"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
|
||||
{
|
||||
"role": "System Manager",
|
||||
"permlevel": 0,
|
||||
"read": 1,
|
||||
"write": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
}
|
||||
],
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
@@ -615,9 +654,7 @@ def prepare_data_for_internal_transfer():
|
||||
|
||||
to_warehouse = create_warehouse("_Test Internal Warehouse GIT A", company=company)
|
||||
|
||||
pr_doc = make_purchase_receipt(
|
||||
company=company, warehouse=warehouse, qty=10, rate=100, do_not_submit=True
|
||||
)
|
||||
pr_doc = make_purchase_receipt(company=company, warehouse=warehouse, qty=10, rate=100, do_not_submit=True)
|
||||
pr_doc.items[0].store = "Inter Transfer Store 1"
|
||||
pr_doc.submit()
|
||||
|
||||
@@ -643,9 +680,7 @@ def prepare_data_for_internal_transfer():
|
||||
|
||||
expense_account = frappe.db.get_value(
|
||||
"Company", company, "stock_adjustment_account"
|
||||
) or frappe.db.get_value(
|
||||
"Account", {"company": company, "account_type": "Expense Account"}, "name"
|
||||
)
|
||||
) or frappe.db.get_value("Account", {"company": company, "account_type": "Expense Account"}, "name")
|
||||
|
||||
return frappe._dict(
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -203,6 +203,7 @@
|
||||
"label": "Allow Alternative Item"
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"bold": 1,
|
||||
"default": "1",
|
||||
"depends_on": "eval:!doc.is_fixed_asset",
|
||||
@@ -240,6 +241,7 @@
|
||||
"label": "Standard Selling Rate"
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"default": "0",
|
||||
"fieldname": "is_fixed_asset",
|
||||
"fieldtype": "Check",
|
||||
@@ -247,6 +249,7 @@
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"depends_on": "is_fixed_asset",
|
||||
"fieldname": "asset_category",
|
||||
"fieldtype": "Link",
|
||||
@@ -897,7 +900,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2023-09-18 15:41:32.688051",
|
||||
"modified": "2024-01-08 18:09:30.225085",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import copy
|
||||
import json
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
@@ -300,9 +299,9 @@ class Item(Document):
|
||||
for d in self.get("uoms"):
|
||||
if cstr(d.uom) in check_list:
|
||||
frappe.throw(
|
||||
_("Unit of Measure {0} has been entered more than once in Conversion Factor Table").format(
|
||||
d.uom
|
||||
)
|
||||
_(
|
||||
"Unit of Measure {0} has been entered more than once in Conversion Factor Table"
|
||||
).format(d.uom)
|
||||
)
|
||||
else:
|
||||
check_list.append(cstr(d.uom))
|
||||
@@ -354,7 +353,7 @@ class Item(Document):
|
||||
frappe.throw(
|
||||
_("{0} entered twice {1} in Item Taxes").format(
|
||||
frappe.bold(d.item_tax_template),
|
||||
"for tax category {0}".format(frappe.bold(d.tax_category)) if d.tax_category else "",
|
||||
f"for tax category {frappe.bold(d.tax_category)}" if d.tax_category else "",
|
||||
)
|
||||
)
|
||||
else:
|
||||
@@ -373,7 +372,9 @@ class Item(Document):
|
||||
)
|
||||
if duplicate:
|
||||
frappe.throw(
|
||||
_("Barcode {0} already used in Item {1}").format(item_barcode.barcode, duplicate[0][0])
|
||||
_("Barcode {0} already used in Item {1}").format(
|
||||
item_barcode.barcode, duplicate[0][0]
|
||||
)
|
||||
)
|
||||
|
||||
item_barcode.barcode_type = (
|
||||
@@ -403,9 +404,9 @@ class Item(Document):
|
||||
warehouse_material_request_type += [(d.get("warehouse"), d.get("material_request_type"))]
|
||||
else:
|
||||
frappe.throw(
|
||||
_("Row #{0}: A reorder entry already exists for warehouse {1} with reorder type {2}.").format(
|
||||
d.idx, d.warehouse, d.material_request_type
|
||||
),
|
||||
_(
|
||||
"Row #{0}: A reorder entry already exists for warehouse {1} with reorder type {2}."
|
||||
).format(d.idx, d.warehouse, d.material_request_type),
|
||||
DuplicateReorderRows,
|
||||
)
|
||||
|
||||
@@ -477,20 +478,21 @@ class Item(Document):
|
||||
|
||||
for dt in ("Sales Taxes and Charges", "Purchase Taxes and Charges"):
|
||||
for d in frappe.db.sql(
|
||||
"""select name, item_wise_tax_detail from `tab{0}`
|
||||
where ifnull(item_wise_tax_detail, '') != ''""".format(
|
||||
dt
|
||||
),
|
||||
f"""select name, item_wise_tax_detail from `tab{dt}`
|
||||
where ifnull(item_wise_tax_detail, '') != ''""",
|
||||
as_dict=1,
|
||||
):
|
||||
|
||||
item_wise_tax_detail = json.loads(d.item_wise_tax_detail)
|
||||
if isinstance(item_wise_tax_detail, dict) and old_name in item_wise_tax_detail:
|
||||
item_wise_tax_detail[new_name] = item_wise_tax_detail[old_name]
|
||||
item_wise_tax_detail.pop(old_name)
|
||||
|
||||
frappe.db.set_value(
|
||||
dt, d.name, "item_wise_tax_detail", json.dumps(item_wise_tax_detail), update_modified=False
|
||||
dt,
|
||||
d.name,
|
||||
"item_wise_tax_detail",
|
||||
json.dumps(item_wise_tax_detail),
|
||||
update_modified=False,
|
||||
)
|
||||
|
||||
def delete_old_bins(self, old_name):
|
||||
@@ -517,9 +519,7 @@ class Item(Document):
|
||||
)
|
||||
|
||||
msg += " <br>"
|
||||
msg += (
|
||||
", ".join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "<br><br>"
|
||||
)
|
||||
msg += ", ".join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "<br><br>"
|
||||
|
||||
msg += _(
|
||||
"Note: To merge the items, create a separate Stock Reconciliation for the old item {0}"
|
||||
@@ -542,12 +542,8 @@ class Item(Document):
|
||||
|
||||
def validate_duplicate_product_bundles_before_merge(self, old_name, new_name):
|
||||
"Block merge if both old and new items have product bundles."
|
||||
old_bundle = frappe.get_value(
|
||||
"Product Bundle", filters={"new_item_code": old_name, "disabled": 0}
|
||||
)
|
||||
new_bundle = frappe.get_value(
|
||||
"Product Bundle", filters={"new_item_code": new_name, "disabled": 0}
|
||||
)
|
||||
old_bundle = frappe.get_value("Product Bundle", filters={"new_item_code": old_name, "disabled": 0})
|
||||
new_bundle = frappe.get_value("Product Bundle", filters={"new_item_code": new_name, "disabled": 0})
|
||||
|
||||
if old_bundle and new_bundle:
|
||||
bundle_link = get_link_to_form("Product Bundle", old_bundle)
|
||||
@@ -572,7 +568,7 @@ class Item(Document):
|
||||
if len(web_items) <= 1:
|
||||
return
|
||||
|
||||
old_web_item = [d.get("name") for d in web_items if d.get("item_code") == old_name][0]
|
||||
old_web_item = next(d.get("name") for d in web_items if d.get("item_code") == old_name)
|
||||
web_item_link = get_link_to_form("Website Item", old_web_item)
|
||||
old_name, new_name = frappe.bold(old_name), frappe.bold(new_name)
|
||||
|
||||
@@ -586,9 +582,7 @@ class Item(Document):
|
||||
def recalculate_bin_qty(self, new_name):
|
||||
from erpnext.stock.stock_balance import repost_stock
|
||||
|
||||
existing_allow_negative_stock = frappe.db.get_value(
|
||||
"Stock Settings", None, "allow_negative_stock"
|
||||
)
|
||||
existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
|
||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
|
||||
|
||||
repost_stock_for_warehouses = frappe.get_all(
|
||||
@@ -605,9 +599,7 @@ class Item(Document):
|
||||
for warehouse in repost_stock_for_warehouses:
|
||||
repost_stock(new_name, warehouse)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock
|
||||
)
|
||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock)
|
||||
|
||||
def update_bom_item_desc(self):
|
||||
if self.is_new():
|
||||
@@ -768,16 +760,14 @@ class Item(Document):
|
||||
return "<br>".join(docnames)
|
||||
|
||||
def table_row(title, body):
|
||||
return """<tr>
|
||||
<td>{0}</td>
|
||||
<td>{1}</td>
|
||||
</tr>""".format(
|
||||
title, body
|
||||
)
|
||||
return f"""<tr>
|
||||
<td>{title}</td>
|
||||
<td>{body}</td>
|
||||
</tr>"""
|
||||
|
||||
rows = ""
|
||||
for docname, attr_list in not_included.items():
|
||||
link = "<a href='/app/Form/Item/{0}'>{0}</a>".format(frappe.bold(_(docname)))
|
||||
link = f"<a href='/app/Form/Item/{frappe.bold(_(docname))}'>{frappe.bold(_(docname))}</a>"
|
||||
rows += table_row(link, body(attr_list))
|
||||
|
||||
error_description = _(
|
||||
@@ -785,17 +775,15 @@ class Item(Document):
|
||||
)
|
||||
|
||||
message = """
|
||||
<div>{0}</div><br>
|
||||
<div>{}</div><br>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<td>{1}</td>
|
||||
<td>{2}</td>
|
||||
<td>{}</td>
|
||||
<td>{}</td>
|
||||
</thead>
|
||||
{3}
|
||||
{}
|
||||
</table>
|
||||
""".format(
|
||||
error_description, _("Variant Items"), _("Attributes"), rows
|
||||
)
|
||||
""".format(error_description, _("Variant Items"), _("Attributes"), rows)
|
||||
|
||||
frappe.throw(message, title=_("Variant Attribute Error"), is_minimizable=True, wide=True)
|
||||
|
||||
@@ -925,7 +913,7 @@ class Item(Document):
|
||||
|
||||
frappe.throw(msg, title=_("Linked with submitted documents"))
|
||||
|
||||
def _get_linked_submitted_documents(self, changed_fields: List[str]) -> Optional[Dict[str, str]]:
|
||||
def _get_linked_submitted_documents(self, changed_fields: list[str]) -> dict[str, str] | None:
|
||||
linked_doctypes = [
|
||||
"Delivery Note Item",
|
||||
"Sales Invoice Item",
|
||||
@@ -1047,6 +1035,7 @@ def validate_cancelled_item(item_code, docstatus=None):
|
||||
frappe.throw(_("Item {0} is cancelled").format(item_code))
|
||||
|
||||
|
||||
@frappe.request_cache
|
||||
def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
||||
"""returns last purchase details in stock uom"""
|
||||
# get last purchase order item details
|
||||
@@ -1087,17 +1076,13 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
||||
last_purchase_receipt and last_purchase_receipt[0].posting_date or "1900-01-01"
|
||||
)
|
||||
|
||||
if last_purchase_order and (
|
||||
purchase_order_date >= purchase_receipt_date or not last_purchase_receipt
|
||||
):
|
||||
if last_purchase_order and (purchase_order_date >= purchase_receipt_date or not last_purchase_receipt):
|
||||
# use purchase order
|
||||
|
||||
last_purchase = last_purchase_order[0]
|
||||
purchase_date = purchase_order_date
|
||||
|
||||
elif last_purchase_receipt and (
|
||||
purchase_receipt_date > purchase_order_date or not last_purchase_order
|
||||
):
|
||||
elif last_purchase_receipt and (purchase_receipt_date > purchase_order_date or not last_purchase_order):
|
||||
# use purchase receipt
|
||||
last_purchase = last_purchase_receipt[0]
|
||||
purchase_date = purchase_receipt_date
|
||||
@@ -1304,7 +1289,7 @@ def set_item_tax_from_hsn_code(item):
|
||||
pass
|
||||
|
||||
|
||||
def validate_item_default_company_links(item_defaults: List[ItemDefault]) -> None:
|
||||
def validate_item_default_company_links(item_defaults: list[ItemDefault]) -> None:
|
||||
for item_default in item_defaults:
|
||||
for doctype, field in [
|
||||
["Warehouse", "default_warehouse"],
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
frappe.listview_settings['Item'] = {
|
||||
add_fields: ["item_name", "stock_uom", "item_group", "image",
|
||||
"has_variants", "end_of_life", "disabled"],
|
||||
frappe.listview_settings["Item"] = {
|
||||
add_fields: ["item_name", "stock_uom", "item_group", "image", "has_variants", "end_of_life", "disabled"],
|
||||
filters: [["disabled", "=", "0"]],
|
||||
|
||||
get_indicator: function(doc) {
|
||||
get_indicator: function (doc) {
|
||||
if (doc.disabled) {
|
||||
return [__("Disabled"), "grey", "disabled,=,Yes"];
|
||||
} else if (doc.end_of_life && doc.end_of_life < frappe.datetime.get_today()) {
|
||||
@@ -17,24 +16,23 @@ frappe.listview_settings['Item'] = {
|
||||
|
||||
reports: [
|
||||
{
|
||||
name: 'Stock Summary',
|
||||
report_type: 'Page',
|
||||
route: 'stock-balance'
|
||||
name: "Stock Summary",
|
||||
report_type: "Page",
|
||||
route: "stock-balance",
|
||||
},
|
||||
{
|
||||
name: 'Stock Ledger',
|
||||
report_type: 'Script Report'
|
||||
name: "Stock Ledger",
|
||||
report_type: "Script Report",
|
||||
},
|
||||
{
|
||||
name: 'Stock Balance',
|
||||
report_type: 'Script Report'
|
||||
name: "Stock Balance",
|
||||
report_type: "Script Report",
|
||||
},
|
||||
{
|
||||
name: 'Stock Projected Qty',
|
||||
report_type: 'Script Report'
|
||||
}
|
||||
|
||||
]
|
||||
name: "Stock Projected Qty",
|
||||
report_type: "Script Report",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
frappe.help.youtube_id["Item"] = "qXaEwld4_Ps";
|
||||
|
||||
@@ -32,7 +32,7 @@ test_ignore = ["BOM"]
|
||||
test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand", "Item Attribute"]
|
||||
|
||||
|
||||
def make_item(item_code=None, properties=None, uoms=None):
|
||||
def make_item(item_code=None, properties=None, uoms=None, barcode=None):
|
||||
if not item_code:
|
||||
item_code = frappe.generate_hash(length=16)
|
||||
|
||||
@@ -61,6 +61,14 @@ def make_item(item_code=None, properties=None, uoms=None):
|
||||
for uom in uoms:
|
||||
item.append("uoms", uom)
|
||||
|
||||
if barcode:
|
||||
item.append(
|
||||
"barcodes",
|
||||
{
|
||||
"barcode": barcode,
|
||||
},
|
||||
)
|
||||
|
||||
item.insert()
|
||||
|
||||
return item
|
||||
@@ -315,7 +323,6 @@ class TestItem(FrappeTestCase):
|
||||
self.assertEqual(value, purchase_item_details.get(key))
|
||||
|
||||
def test_item_default_validations(self):
|
||||
|
||||
with self.assertRaises(frappe.ValidationError) as ve:
|
||||
make_item(
|
||||
"Bad Item defaults",
|
||||
@@ -469,9 +476,7 @@ class TestItem(FrappeTestCase):
|
||||
|
||||
self.assertFalse(frappe.db.exists("Item", old))
|
||||
|
||||
self.assertTrue(
|
||||
frappe.db.get_value("Bin", {"item_code": new, "warehouse": "_Test Warehouse - _TC"})
|
||||
)
|
||||
self.assertTrue(frappe.db.get_value("Bin", {"item_code": new, "warehouse": "_Test Warehouse - _TC"}))
|
||||
self.assertTrue(
|
||||
frappe.db.get_value("Bin", {"item_code": new, "warehouse": "_Test Warehouse 1 - _TC"})
|
||||
)
|
||||
@@ -524,7 +529,7 @@ class TestItem(FrappeTestCase):
|
||||
def test_item_variant_by_manufacturer(self):
|
||||
template = make_item(
|
||||
"_Test Item Variant By Manufacturer", {"has_variants": 1, "variant_based_on": "Manufacturer"}
|
||||
)
|
||||
).name
|
||||
|
||||
for manufacturer in ["DFSS", "DASA", "ASAAS"]:
|
||||
if not frappe.db.exists("Manufacturer", manufacturer):
|
||||
@@ -715,9 +720,7 @@ class TestItem(FrappeTestCase):
|
||||
|
||||
@change_settings("Stock Settings", {"sample_retention_warehouse": "_Test Warehouse - _TC"})
|
||||
def test_retain_sample(self):
|
||||
item = make_item(
|
||||
"_TestRetainSample", {"has_batch_no": 1, "retain_sample": 1, "sample_quantity": 1}
|
||||
)
|
||||
item = make_item("_TestRetainSample", {"has_batch_no": 1, "retain_sample": 1, "sample_quantity": 1})
|
||||
|
||||
self.assertEqual(item.has_batch_no, 1)
|
||||
self.assertEqual(item.retain_sample, 1)
|
||||
@@ -790,7 +793,7 @@ class TestItem(FrappeTestCase):
|
||||
def test_customer_codes_length(self):
|
||||
"""Check if item code with special characters are allowed."""
|
||||
item = make_item(properties={"item_code": "Test Item Code With Special Characters"})
|
||||
for row in range(3):
|
||||
for _row in range(3):
|
||||
item.append("customer_items", {"ref_code": frappe.generate_hash("", 120)})
|
||||
item.save()
|
||||
self.assertTrue(len(item.customer_code) > 140)
|
||||
@@ -831,9 +834,7 @@ class TestItem(FrappeTestCase):
|
||||
make_property_setter("Item", None, "search_fields", "item_name", "Data", for_doctype="Doctype")
|
||||
|
||||
item = make_item(properties={"item_name": "Test Item", "description": "Test Description"})
|
||||
data = item_query(
|
||||
"Item", "Test Item", "", 0, 20, filters={"item_name": "Test Item"}, as_dict=True
|
||||
)
|
||||
data = item_query("Item", "Test Item", "", 0, 20, filters={"item_name": "Test Item"}, as_dict=True)
|
||||
self.assertEqual(data[0].name, item.name)
|
||||
self.assertEqual(data[0].item_name, item.item_name)
|
||||
self.assertTrue("description" not in data[0])
|
||||
@@ -841,9 +842,7 @@ class TestItem(FrappeTestCase):
|
||||
make_property_setter(
|
||||
"Item", None, "search_fields", "item_name, description", "Data", for_doctype="Doctype"
|
||||
)
|
||||
data = item_query(
|
||||
"Item", "Test Item", "", 0, 20, filters={"item_name": "Test Item"}, as_dict=True
|
||||
)
|
||||
data = item_query("Item", "Test Item", "", 0, 20, filters={"item_name": "Test Item"}, as_dict=True)
|
||||
self.assertEqual(data[0].name, item.name)
|
||||
self.assertEqual(data[0].item_name, item.item_name)
|
||||
self.assertEqual(data[0].description, item.description)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Item Alternative', {
|
||||
setup: function(frm) {
|
||||
frappe.ui.form.on("Item Alternative", {
|
||||
setup: function (frm) {
|
||||
frm.fields_dict.item_code.get_query = () => {
|
||||
return {
|
||||
filters: {
|
||||
'allow_alternative_item': 1
|
||||
}
|
||||
allow_alternative_item: 1,
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -30,9 +30,7 @@ class ItemAlternative(Document):
|
||||
"allow_alternative_item",
|
||||
]
|
||||
item_data = frappe.db.get_value("Item", self.item_code, fields, as_dict=1)
|
||||
alternative_item_data = frappe.db.get_value(
|
||||
"Item", self.alternative_item_code, fields, as_dict=1
|
||||
)
|
||||
alternative_item_data = frappe.db.get_value("Item", self.alternative_item_code, fields, as_dict=1)
|
||||
|
||||
for field in fields:
|
||||
if item_data.get(field) != alternative_item_data.get(field):
|
||||
@@ -72,14 +70,12 @@ class ItemAlternative(Document):
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_alternative_items(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(
|
||||
""" (select alternative_item_code from `tabItem Alternative`
|
||||
f""" (select alternative_item_code from `tabItem Alternative`
|
||||
where item_code = %(item_code)s and alternative_item_code like %(txt)s)
|
||||
union
|
||||
(select item_code from `tabItem Alternative`
|
||||
where alternative_item_code = %(item_code)s and item_code like %(txt)s
|
||||
and two_way = 1) limit {1} offset {0}
|
||||
""".format(
|
||||
start, page_len
|
||||
),
|
||||
and two_way = 1) limit {page_len} offset {start}
|
||||
""",
|
||||
{"item_code": filters.get("item_code"), "txt": "%" + txt + "%"},
|
||||
)
|
||||
|
||||
@@ -54,9 +54,7 @@ class TestItemAlternative(FrappeTestCase):
|
||||
"fg_item_qty": 5,
|
||||
},
|
||||
]
|
||||
sco = get_subcontracting_order(
|
||||
service_items=service_items, supplier_warehouse=supplier_warehouse
|
||||
)
|
||||
sco = get_subcontracting_order(service_items=service_items, supplier_warehouse=supplier_warehouse)
|
||||
rm_items = [
|
||||
{
|
||||
"item_code": "Test Finished Goods - A",
|
||||
@@ -106,9 +104,7 @@ class TestItemAlternative(FrappeTestCase):
|
||||
"reserved_qty_for_sub_contract",
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
after_transfer_reserved_qty_for_sub_contract, flt(reserved_qty_for_sub_contract - 5)
|
||||
)
|
||||
self.assertEqual(after_transfer_reserved_qty_for_sub_contract, flt(reserved_qty_for_sub_contract - 5))
|
||||
|
||||
scr = make_subcontracting_receipt(sco.name)
|
||||
scr.save()
|
||||
@@ -159,9 +155,7 @@ class TestItemAlternative(FrappeTestCase):
|
||||
"reserved_qty_for_production",
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
reserved_qty_for_production_after_transfer, flt(reserved_qty_for_production - 5)
|
||||
)
|
||||
self.assertEqual(reserved_qty_for_production_after_transfer, flt(reserved_qty_for_production - 5))
|
||||
ste1 = frappe.get_doc(make_stock_entry(pro_order.name, "Manufacture", 5))
|
||||
|
||||
status = False
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Item Attribute', {
|
||||
|
||||
});
|
||||
frappe.ui.form.on("Item Attribute", {});
|
||||
|
||||
@@ -75,7 +75,9 @@ class ItemAttribute(Document):
|
||||
values, abbrs = [], []
|
||||
for d in self.item_attribute_values:
|
||||
if d.attribute_value.lower() in map(str.lower, values):
|
||||
frappe.throw(_("Attribute value: {0} must appear only once").format(d.attribute_value.title()))
|
||||
frappe.throw(
|
||||
_("Attribute value: {0} must appear only once").format(d.attribute_value.title())
|
||||
)
|
||||
values.append(d.attribute_value)
|
||||
|
||||
if d.abbr.lower() in map(str.lower, abbrs):
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Item Manufacturer', {
|
||||
frappe.ui.form.on("Item Manufacturer", {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
|
||||
@@ -41,7 +41,9 @@ class ItemManufacturer(Document):
|
||||
# if unchecked and default in Item master, clear it.
|
||||
if default_manufacturer == self.manufacturer and default_part_no == self.manufacturer_part_no:
|
||||
frappe.db.set_value(
|
||||
"Item", item.name, {"default_item_manufacturer": None, "default_manufacturer_part_no": None}
|
||||
"Item",
|
||||
item.name,
|
||||
{"default_item_manufacturer": None, "default_manufacturer_part_no": None},
|
||||
)
|
||||
|
||||
elif self.is_default:
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
frappe.ui.form.on("Item Price", {
|
||||
setup(frm) {
|
||||
frm.set_query("item_code", function() {
|
||||
frm.set_query("item_code", function () {
|
||||
return {
|
||||
filters: {
|
||||
"has_variants": 0
|
||||
}
|
||||
has_variants: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
@@ -23,15 +23,18 @@ frappe.ui.form.on("Item Price", {
|
||||
frm.add_fetch("item_code", "description", "item_description");
|
||||
frm.add_fetch("item_code", "stock_uom", "uom");
|
||||
|
||||
frm.set_df_property("bulk_import_help", "options",
|
||||
'<a href="/app/data-import-tool/Item Price">' + __("Import in Bulk") + '</a>');
|
||||
frm.set_df_property(
|
||||
"bulk_import_help",
|
||||
"options",
|
||||
'<a href="/app/data-import-tool/Item Price">' + __("Import in Bulk") + "</a>"
|
||||
);
|
||||
|
||||
frm.set_query('batch_no', function() {
|
||||
frm.set_query("batch_no", function () {
|
||||
return {
|
||||
filters: {
|
||||
'item': frm.doc.item_code
|
||||
}
|
||||
item: frm.doc.item_code,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -104,7 +104,8 @@
|
||||
"in_standard_filter": 1,
|
||||
"label": "Price List",
|
||||
"options": "Price List",
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
@@ -220,7 +221,7 @@
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-11-15 08:26:04.041861",
|
||||
"modified": "2024-03-13 12:23:39.630290",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item Price",
|
||||
|
||||
@@ -40,7 +40,7 @@ class ItemPrice(Document):
|
||||
|
||||
if not price_list_details:
|
||||
link = frappe.utils.get_link_to_form("Price List", self.price_list)
|
||||
frappe.throw("The price list {0} does not exist or is disabled".format(link))
|
||||
frappe.throw(f"The price list {link} does not exist or is disabled")
|
||||
|
||||
self.buying, self.selling, self.currency = price_list_details
|
||||
|
||||
@@ -57,7 +57,6 @@ class ItemPrice(Document):
|
||||
frappe.throw(_(msg))
|
||||
|
||||
def check_duplicates(self):
|
||||
|
||||
item_price = frappe.qb.DocType("Item Price")
|
||||
|
||||
query = (
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
frappe.listview_settings['Item Price'] = {
|
||||
frappe.listview_settings["Item Price"] = {
|
||||
hide_name_column: true,
|
||||
};
|
||||
|
||||
@@ -1,24 +1,39 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Item Variant Settings', {
|
||||
refresh: function(frm) {
|
||||
frappe.ui.form.on("Item Variant Settings", {
|
||||
refresh: function (frm) {
|
||||
const allow_fields = [];
|
||||
|
||||
const existing_fields = frm.doc.fields.map(row => row.field_name);
|
||||
const exclude_fields = [...existing_fields, "naming_series", "item_code", "item_name",
|
||||
"published_in_website", "standard_rate", "opening_stock", "image",
|
||||
"variant_of", "valuation_rate", "barcodes", "has_variants", "attributes"];
|
||||
const existing_fields = frm.doc.fields.map((row) => row.field_name);
|
||||
const exclude_fields = [
|
||||
...existing_fields,
|
||||
"naming_series",
|
||||
"item_code",
|
||||
"item_name",
|
||||
"published_in_website",
|
||||
"standard_rate",
|
||||
"opening_stock",
|
||||
"image",
|
||||
"variant_of",
|
||||
"valuation_rate",
|
||||
"barcodes",
|
||||
"has_variants",
|
||||
"attributes",
|
||||
];
|
||||
|
||||
const exclude_field_types = ['HTML', 'Section Break', 'Column Break', 'Button', 'Read Only'];
|
||||
const exclude_field_types = ["HTML", "Section Break", "Column Break", "Button", "Read Only"];
|
||||
|
||||
frappe.model.with_doctype('Item', () => {
|
||||
frappe.model.with_doctype("Item", () => {
|
||||
const field_label_map = {};
|
||||
frappe.get_meta('Item').fields.forEach(d => {
|
||||
frappe.get_meta("Item").fields.forEach((d) => {
|
||||
field_label_map[d.fieldname] = __(d.label) + ` (${d.fieldname})`;
|
||||
|
||||
if (!in_list(exclude_field_types, d.fieldtype)
|
||||
&& !d.no_copy && !in_list(exclude_fields, d.fieldname)) {
|
||||
if (
|
||||
!in_list(exclude_field_types, d.fieldtype) &&
|
||||
!d.no_copy &&
|
||||
!in_list(exclude_fields, d.fieldname)
|
||||
) {
|
||||
allow_fields.push({
|
||||
label: field_label_map[d.fieldname],
|
||||
value: d.fieldname,
|
||||
@@ -33,9 +48,7 @@ frappe.ui.form.on('Item Variant Settings', {
|
||||
});
|
||||
}
|
||||
|
||||
frm.fields_dict.fields.grid.update_docfield_property(
|
||||
'field_name', 'options', allow_fields
|
||||
);
|
||||
frm.fields_dict.fields.grid.update_docfield_property("field_name", "options", allow_fields);
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import typing
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
@@ -8,7 +8,7 @@ from frappe.model.document import Document
|
||||
|
||||
|
||||
class ItemVariantSettings(Document):
|
||||
invalid_fields_for_copy_fields_in_variants = ["barcodes"]
|
||||
invalid_fields_for_copy_fields_in_variants: typing.ClassVar[list] = ["barcodes"]
|
||||
|
||||
def set_default_fields(self):
|
||||
self.fields = []
|
||||
@@ -24,7 +24,6 @@ class ItemVariantSettings(Document):
|
||||
"description",
|
||||
"variant_of",
|
||||
"valuation_rate",
|
||||
"description",
|
||||
"barcodes",
|
||||
"has_variants",
|
||||
"attributes",
|
||||
@@ -50,4 +49,6 @@ class ItemVariantSettings(Document):
|
||||
def validate(self):
|
||||
for d in self.fields:
|
||||
if d.field_name in self.invalid_fields_for_copy_fields_in_variants:
|
||||
frappe.throw(_("Cannot set the field <b>{0}</b> for copying in variants").format(d.field_name))
|
||||
frappe.throw(
|
||||
_("Cannot set the field <b>{0}</b> for copying in variants").format(d.field_name)
|
||||
)
|
||||
|
||||
@@ -38,6 +38,7 @@ class LandedCostVoucher(Document):
|
||||
def validate(self):
|
||||
self.check_mandatory()
|
||||
self.validate_receipt_documents()
|
||||
self.validate_line_items()
|
||||
init_landed_taxes_and_totals(self)
|
||||
self.set_total_taxes_and_charges()
|
||||
if not self.get("items"):
|
||||
@@ -45,6 +46,26 @@ class LandedCostVoucher(Document):
|
||||
|
||||
self.set_applicable_charges_on_item()
|
||||
|
||||
def validate_line_items(self):
|
||||
for d in self.get("items"):
|
||||
if (
|
||||
d.docstatus == 0
|
||||
and d.purchase_receipt_item
|
||||
and not frappe.db.exists(
|
||||
d.receipt_document_type + " Item",
|
||||
{"name": d.purchase_receipt_item, "parent": d.receipt_document},
|
||||
)
|
||||
):
|
||||
frappe.throw(
|
||||
_("Row {0}: {2} Item {1} does not exist in {2} {3}").format(
|
||||
d.idx,
|
||||
frappe.bold(d.purchase_receipt_item),
|
||||
d.receipt_document_type,
|
||||
frappe.bold(d.receipt_document),
|
||||
),
|
||||
title=_("Incorrect Reference Document (Purchase Receipt Item)"),
|
||||
)
|
||||
|
||||
def check_mandatory(self):
|
||||
if not self.get("purchase_receipts"):
|
||||
frappe.throw(_("Please enter Receipt Document"))
|
||||
@@ -55,13 +76,13 @@ class LandedCostVoucher(Document):
|
||||
for d in self.get("purchase_receipts"):
|
||||
docstatus = frappe.db.get_value(d.receipt_document_type, d.receipt_document, "docstatus")
|
||||
if docstatus != 1:
|
||||
msg = (
|
||||
f"Row {d.idx}: {d.receipt_document_type} {frappe.bold(d.receipt_document)} must be submitted"
|
||||
)
|
||||
msg = f"Row {d.idx}: {d.receipt_document_type} {frappe.bold(d.receipt_document)} must be submitted"
|
||||
frappe.throw(_(msg), title=_("Invalid Document"))
|
||||
|
||||
if d.receipt_document_type == "Purchase Invoice":
|
||||
update_stock = frappe.db.get_value(d.receipt_document_type, d.receipt_document, "update_stock")
|
||||
update_stock = frappe.db.get_value(
|
||||
d.receipt_document_type, d.receipt_document, "update_stock"
|
||||
)
|
||||
if not update_stock:
|
||||
msg = _("Row {0}: Purchase Invoice {1} has no stock impact.").format(
|
||||
d.idx, frappe.bold(d.receipt_document)
|
||||
@@ -111,7 +132,8 @@ class LandedCostVoucher(Document):
|
||||
)
|
||||
|
||||
item.applicable_charges = flt(
|
||||
flt(item.get(based_on_field)) * (flt(self.total_taxes_and_charges) / flt(total_item_cost)),
|
||||
flt(item.get(based_on_field))
|
||||
* (flt(self.total_taxes_and_charges) / flt(total_item_cost)),
|
||||
item.precision("applicable_charges"),
|
||||
)
|
||||
total_charges += item.applicable_charges
|
||||
@@ -122,6 +144,13 @@ class LandedCostVoucher(Document):
|
||||
self.get("items")[item_count - 1].applicable_charges += diff
|
||||
|
||||
def validate_applicable_charges_for_item(self):
|
||||
if self.distribute_charges_based_on == "Distribute Manually" and len(self.taxes) > 1:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Please keep one Applicable Charges, when 'Distribute Charges Based On' is 'Distribute Manually'. For more charges, please create another Landed Cost Voucher."
|
||||
)
|
||||
)
|
||||
|
||||
based_on = self.distribute_charges_based_on.lower()
|
||||
|
||||
if based_on != "distribute manually":
|
||||
@@ -167,7 +196,8 @@ class LandedCostVoucher(Document):
|
||||
for d in self.get("purchase_receipts"):
|
||||
doc = frappe.get_doc(d.receipt_document_type, d.receipt_document)
|
||||
# check if there are {qty} assets created and linked to this receipt document
|
||||
self.validate_asset_qty_and_status(d.receipt_document_type, doc)
|
||||
if self.docstatus != 2:
|
||||
self.validate_asset_qty_and_status(d.receipt_document_type, doc)
|
||||
|
||||
# set landed cost voucher amount in pr item
|
||||
doc.set_landed_cost_voucher_amount()
|
||||
@@ -201,28 +231,28 @@ class LandedCostVoucher(Document):
|
||||
for item in self.get("items"):
|
||||
if item.is_fixed_asset:
|
||||
receipt_document_type = (
|
||||
"purchase_invoice" if item.receipt_document_type == "Purchase Invoice" else "purchase_receipt"
|
||||
"purchase_invoice"
|
||||
if item.receipt_document_type == "Purchase Invoice"
|
||||
else "purchase_receipt"
|
||||
)
|
||||
docs = frappe.db.get_all(
|
||||
"Asset",
|
||||
filters={receipt_document_type: item.receipt_document, "item_code": item.item_code},
|
||||
fields=["name", "docstatus"],
|
||||
)
|
||||
if not docs or len(docs) != item.qty:
|
||||
if not docs or len(docs) < item.qty:
|
||||
frappe.throw(
|
||||
_(
|
||||
"There are not enough asset created or linked to {0}. Please create or link {1} Assets with respective document."
|
||||
).format(item.receipt_document, item.qty)
|
||||
"There are only {0} asset created or linked to {1}. Please create or link {2} Assets with respective document."
|
||||
).format(len(docs), item.receipt_document, item.qty)
|
||||
)
|
||||
if docs:
|
||||
for d in docs:
|
||||
if d.docstatus == 1:
|
||||
frappe.throw(
|
||||
_(
|
||||
"{2} <b>{0}</b> has submitted Assets. Remove Item <b>{1}</b> from table to continue."
|
||||
).format(
|
||||
item.receipt_document, item.item_code, item.receipt_document_type
|
||||
)
|
||||
"{0} <b>{1}</b> has submitted Assets. Remove Item <b>{2}</b> from table to continue."
|
||||
).format(item.receipt_document_type, item.receipt_document, item.item_code)
|
||||
)
|
||||
|
||||
def update_rate_in_serial_no_for_non_asset_items(self, receipt_document):
|
||||
@@ -231,10 +261,10 @@ class LandedCostVoucher(Document):
|
||||
serial_nos = get_serial_nos(item.serial_no)
|
||||
if serial_nos:
|
||||
frappe.db.sql(
|
||||
"update `tabSerial No` set purchase_rate=%s where name in ({0})".format(
|
||||
"update `tabSerial No` set purchase_rate=%s where name in ({})".format(
|
||||
", ".join(["%s"] * len(serial_nos))
|
||||
),
|
||||
tuple([item.valuation_rate] + serial_nos),
|
||||
tuple([item.valuation_rate, *serial_nos]),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -62,9 +62,7 @@ class TestLandedCostVoucher(FrappeTestCase):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction
|
||||
)
|
||||
self.assertEqual(last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction)
|
||||
self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 25.0)
|
||||
|
||||
# assert after submit
|
||||
@@ -87,7 +85,6 @@ class TestLandedCostVoucher(FrappeTestCase):
|
||||
self.assertPurchaseReceiptLCVGLEntries(pr)
|
||||
|
||||
def assertPurchaseReceiptLCVGLEntries(self, pr):
|
||||
|
||||
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
@@ -170,9 +167,7 @@ class TestLandedCostVoucher(FrappeTestCase):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction
|
||||
)
|
||||
self.assertEqual(last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction)
|
||||
self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 50.0)
|
||||
|
||||
def test_landed_cost_voucher_for_zero_purchase_rate(self):
|
||||
@@ -229,7 +224,6 @@ class TestLandedCostVoucher(FrappeTestCase):
|
||||
)
|
||||
|
||||
def test_landed_cost_voucher_against_purchase_invoice(self):
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
update_stock=1,
|
||||
posting_date=frappe.utils.nowdate(),
|
||||
@@ -274,9 +268,7 @@ class TestLandedCostVoucher(FrappeTestCase):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction
|
||||
)
|
||||
self.assertEqual(last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction)
|
||||
|
||||
self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 50.0)
|
||||
|
||||
@@ -365,9 +357,7 @@ class TestLandedCostVoucher(FrappeTestCase):
|
||||
|
||||
new_purchase_rate = serial_no_rate + charges
|
||||
|
||||
serial_no = frappe.db.get_value(
|
||||
"Serial No", serial_no, ["warehouse", "purchase_rate"], as_dict=1
|
||||
)
|
||||
serial_no = frappe.db.get_value("Serial No", serial_no, ["warehouse", "purchase_rate"], as_dict=1)
|
||||
|
||||
self.assertEqual(serial_no.purchase_rate, new_purchase_rate)
|
||||
|
||||
@@ -392,7 +382,7 @@ class TestLandedCostVoucher(FrappeTestCase):
|
||||
do_not_save=True,
|
||||
)
|
||||
pr.items[0].cost_center = "Main - TCP1"
|
||||
for x in range(2):
|
||||
for _x in range(2):
|
||||
pr.append(
|
||||
"items",
|
||||
{
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Manufacturer', {
|
||||
refresh: function(frm) {
|
||||
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Manufacturer' };
|
||||
frappe.ui.form.on("Manufacturer", {
|
||||
refresh: function (frm) {
|
||||
frappe.dynamic_link = { doc: frm.doc, fieldname: "name", doctype: "Manufacturer" };
|
||||
if (frm.doc.__islocal) {
|
||||
hide_field(['address_html','contact_html']);
|
||||
hide_field(["address_html", "contact_html"]);
|
||||
frappe.contacts.clear_address_and_contact(frm);
|
||||
}
|
||||
else {
|
||||
unhide_field(['address_html','contact_html']);
|
||||
} else {
|
||||
unhide_field(["address_html", "contact_html"]);
|
||||
frappe.contacts.render_address_and_contact(frm);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -199,9 +199,9 @@ frappe.ui.form.on('Material Request', {
|
||||
|
||||
get_item_data: function(frm, item, overwrite_warehouse=false) {
|
||||
if (item && !item.item_code) { return; }
|
||||
frm.call({
|
||||
|
||||
frappe.call({
|
||||
method: "erpnext.stock.get_item_details.get_item_details",
|
||||
child: item,
|
||||
args: {
|
||||
args: {
|
||||
item_code: item.item_code,
|
||||
@@ -226,12 +226,22 @@ frappe.ui.form.on('Material Request', {
|
||||
},
|
||||
callback: function(r) {
|
||||
const d = item;
|
||||
const qty_fields = ['actual_qty', 'projected_qty', 'min_order_qty'];
|
||||
const allow_to_change_fields = ['actual_qty', 'projected_qty', 'min_order_qty', 'item_name', 'description', 'stock_uom', 'uom', 'conversion_factor', 'stock_qty'];
|
||||
|
||||
if(!r.exc) {
|
||||
$.each(r.message, function(k, v) {
|
||||
if(!d[k] || in_list(qty_fields, k)) d[k] = v;
|
||||
$.each(r.message, function(key, value) {
|
||||
if(!d[key] || allow_to_change_fields.includes(key)) {
|
||||
d[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
if (d.price_list_rate != r.message.price_list_rate) {
|
||||
d.rate = 0.0;
|
||||
d.price_list_rate = r.message.price_list_rate;
|
||||
frappe.model.set_value(d.doctype, d.name, "rate", d.price_list_rate);
|
||||
}
|
||||
|
||||
refresh_field("items");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -243,7 +253,7 @@ frappe.ui.form.on('Material Request', {
|
||||
fields: [
|
||||
{"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
|
||||
options:"BOM", reqd: 1, get_query: function() {
|
||||
return {filters: { docstatus:1 }};
|
||||
return {filters: { docstatus:1, "is_active": 1 }};
|
||||
}},
|
||||
{"fieldname":"warehouse", "fieldtype":"Link", "label":__("For Warehouse"),
|
||||
options:"Warehouse", reqd: 1},
|
||||
@@ -428,9 +438,11 @@ frappe.ui.form.on("Material Request Item", {
|
||||
frm.events.get_item_data(frm, item, false);
|
||||
},
|
||||
|
||||
rate: function(frm, doctype, name) {
|
||||
rate(frm, doctype, name) {
|
||||
const item = locals[doctype][name];
|
||||
frm.events.get_item_data(frm, item, false);
|
||||
item.amount = flt(item.qty) * flt(item.rate);
|
||||
frappe.model.set_value(doctype, name, "amount", item.amount);
|
||||
refresh_field("amount", item.name, item.parentfield);
|
||||
},
|
||||
|
||||
item_code: function(frm, doctype, name) {
|
||||
@@ -450,7 +462,12 @@ frappe.ui.form.on("Material Request Item", {
|
||||
set_schedule_date(frm);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
conversion_factor: function(frm, doctype, name) {
|
||||
const item = locals[doctype][name];
|
||||
frm.events.get_item_data(frm, item, false);
|
||||
},
|
||||
});
|
||||
|
||||
erpnext.buying.MaterialRequestController = class MaterialRequestController extends erpnext.buying.BuyingController {
|
||||
@@ -515,6 +532,13 @@ erpnext.buying.MaterialRequestController = class MaterialRequestController exten
|
||||
schedule_date() {
|
||||
set_schedule_date(this.frm);
|
||||
}
|
||||
|
||||
qty(doc, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
row.amount = flt(row.qty) * flt(row.rate);
|
||||
frappe.model.set_value(cdt, cdn, "amount", row.amount);
|
||||
refresh_field("amount", row.name, row.parentfield);
|
||||
}
|
||||
};
|
||||
|
||||
// for backward compatibility: combine new and previous states
|
||||
|
||||
@@ -33,10 +33,10 @@ class MaterialRequest(BuyingController):
|
||||
so_items = {} # Format --> {'SO/00001': {'Item/001': 120, 'Item/002': 24}}
|
||||
for d in self.get("items"):
|
||||
if d.sales_order:
|
||||
if not d.sales_order in so_items:
|
||||
if d.sales_order not in so_items:
|
||||
so_items[d.sales_order] = {d.item_code: flt(d.qty)}
|
||||
else:
|
||||
if not d.item_code in so_items[d.sales_order]:
|
||||
if d.item_code not in so_items[d.sales_order]:
|
||||
so_items[d.sales_order][d.item_code] = flt(d.qty)
|
||||
else:
|
||||
so_items[d.sales_order][d.item_code] += flt(d.qty)
|
||||
@@ -61,13 +61,13 @@ class MaterialRequest(BuyingController):
|
||||
|
||||
if actual_so_qty and (flt(so_items[so_no][item]) + already_indented > actual_so_qty):
|
||||
frappe.throw(
|
||||
_("Material Request of maximum {0} can be made for Item {1} against Sales Order {2}").format(
|
||||
actual_so_qty - already_indented, item, so_no
|
||||
)
|
||||
_(
|
||||
"Material Request of maximum {0} can be made for Item {1} against Sales Order {2}"
|
||||
).format(actual_so_qty - already_indented, item, so_no)
|
||||
)
|
||||
|
||||
def validate(self):
|
||||
super(MaterialRequest, self).validate()
|
||||
super().validate()
|
||||
|
||||
self.validate_schedule_date()
|
||||
self.check_for_on_hold_or_closed_status("Sales Order", "sales_order")
|
||||
@@ -123,7 +123,9 @@ class MaterialRequest(BuyingController):
|
||||
def on_submit(self):
|
||||
self.update_requested_qty_in_production_plan()
|
||||
self.update_requested_qty()
|
||||
if self.material_request_type == "Purchase":
|
||||
if self.material_request_type == "Purchase" and frappe.db.exists(
|
||||
"Budget", {"applicable_on_material_request": 1, "docstatus": 1}
|
||||
):
|
||||
self.validate_budget()
|
||||
|
||||
def before_save(self):
|
||||
@@ -139,12 +141,8 @@ class MaterialRequest(BuyingController):
|
||||
self.set_status(update=True, status="Cancelled")
|
||||
|
||||
def check_modified_date(self):
|
||||
mod_db = frappe.db.sql(
|
||||
"""select modified from `tabMaterial Request` where name = %s""", self.name
|
||||
)
|
||||
date_diff = frappe.db.sql(
|
||||
"""select TIMEDIFF('%s', '%s')""" % (mod_db[0][0], cstr(self.modified))
|
||||
)
|
||||
mod_db = frappe.db.sql("""select modified from `tabMaterial Request` where name = %s""", self.name)
|
||||
date_diff = frappe.db.sql(f"""select TIMEDIFF('{mod_db[0][0]}', '{cstr(self.modified)}')""")
|
||||
|
||||
if date_diff and date_diff[0][0]:
|
||||
frappe.throw(_("{0} {1} has been modified. Please refresh.").format(_(self.doctype), self.name))
|
||||
@@ -324,9 +322,7 @@ def update_completed_and_requested_qty(stock_entry, method):
|
||||
|
||||
|
||||
def set_missing_values(source, target_doc):
|
||||
if target_doc.doctype == "Purchase Order" and getdate(target_doc.schedule_date) < getdate(
|
||||
nowdate()
|
||||
):
|
||||
if target_doc.doctype == "Purchase Order" and getdate(target_doc.schedule_date) < getdate(nowdate()):
|
||||
target_doc.schedule_date = None
|
||||
target_doc.run_method("set_missing_values")
|
||||
target_doc.run_method("calculate_taxes_and_totals")
|
||||
@@ -415,6 +411,7 @@ def make_purchase_order(source_name, target_doc=None, args=None):
|
||||
postprocess,
|
||||
)
|
||||
|
||||
doclist.set_onload("load_after_mapping", False)
|
||||
return doclist
|
||||
|
||||
|
||||
@@ -455,9 +452,7 @@ def make_purchase_order_based_on_supplier(source_name, target_doc=None, args=Non
|
||||
target_doc.schedule_date = None
|
||||
target_doc.set(
|
||||
"items",
|
||||
[
|
||||
d for d in target_doc.get("items") if d.get("item_code") in supplier_items and d.get("qty") > 0
|
||||
],
|
||||
[d for d in target_doc.get("items") if d.get("item_code") in supplier_items and d.get("qty") > 0],
|
||||
)
|
||||
|
||||
set_missing_values(source, target_doc)
|
||||
@@ -509,7 +504,7 @@ def get_material_requests_based_on_supplier(doctype, txt, searchfield, start, pa
|
||||
|
||||
if filters.get("transaction_date"):
|
||||
date = filters.get("transaction_date")[1]
|
||||
conditions += "and mr.transaction_date between '{0}' and '{1}' ".format(date[0], date[1])
|
||||
conditions += f"and mr.transaction_date between '{date[0]}' and '{date[1]}' "
|
||||
|
||||
supplier = filters.get("supplier")
|
||||
supplier_items = get_items_based_on_default_supplier(supplier)
|
||||
@@ -521,18 +516,18 @@ def get_material_requests_based_on_supplier(doctype, txt, searchfield, start, pa
|
||||
"""select distinct mr.name, transaction_date,company
|
||||
from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
|
||||
where mr.name = mr_item.parent
|
||||
and mr_item.item_code in ({0})
|
||||
and mr_item.item_code in ({})
|
||||
and mr.material_request_type = 'Purchase'
|
||||
and mr.per_ordered < 99.99
|
||||
and mr.docstatus = 1
|
||||
and mr.status != 'Stopped'
|
||||
and mr.company = %s
|
||||
{1}
|
||||
{}
|
||||
order by mr_item.item_code ASC
|
||||
limit {2} offset {3} """.format(
|
||||
limit {} offset {} """.format(
|
||||
", ".join(["%s"] * len(supplier_items)), conditions, cint(page_len), cint(start)
|
||||
),
|
||||
tuple(supplier_items) + (filters.get("company"),),
|
||||
(*tuple(supplier_items), filters.get("company")),
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
@@ -547,17 +542,28 @@ def get_default_supplier_query(doctype, txt, searchfield, start, page_len, filte
|
||||
for d in doc.items:
|
||||
item_list.append(d.item_code)
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select default_supplier
|
||||
from `tabItem Default`
|
||||
where parent in ({0}) and
|
||||
default_supplier IS NOT NULL
|
||||
""".format(
|
||||
", ".join(["%s"] * len(item_list))
|
||||
),
|
||||
tuple(item_list),
|
||||
supplier = frappe.qb.DocType("Supplier")
|
||||
item_default = frappe.qb.DocType("Item Default")
|
||||
query = (
|
||||
frappe.qb.from_(supplier)
|
||||
.left_join(item_default)
|
||||
.on(supplier.name == item_default.default_supplier)
|
||||
.select(item_default.default_supplier)
|
||||
.where(
|
||||
(item_default.parent.isin(item_list))
|
||||
& (item_default.default_supplier.notnull())
|
||||
& (supplier[searchfield].like(f"%{txt}%"))
|
||||
)
|
||||
.offset(start)
|
||||
.limit(page_len)
|
||||
)
|
||||
|
||||
meta = frappe.get_meta("Supplier")
|
||||
if meta.show_title_field_in_link and meta.title_field:
|
||||
query = query.select(supplier[meta.title_field])
|
||||
|
||||
return query.run(as_dict=False)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_supplier_quotation(source_name, target_doc=None):
|
||||
@@ -649,7 +655,10 @@ def make_stock_entry(source_name, target_doc=None):
|
||||
"doctype": "Stock Entry",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1],
|
||||
"material_request_type": ["in", ["Material Transfer", "Material Issue", "Customer Provided"]],
|
||||
"material_request_type": [
|
||||
"in",
|
||||
["Material Transfer", "Material Issue", "Customer Provided"],
|
||||
],
|
||||
},
|
||||
},
|
||||
"Material Request Item": {
|
||||
@@ -679,9 +688,7 @@ def raise_work_orders(material_request):
|
||||
mr = frappe.get_doc("Material Request", material_request)
|
||||
errors = []
|
||||
work_orders = []
|
||||
default_wip_warehouse = frappe.db.get_single_value(
|
||||
"Manufacturing Settings", "default_wip_warehouse"
|
||||
)
|
||||
default_wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse")
|
||||
|
||||
for d in mr.items:
|
||||
if (d.stock_qty - d.ordered_qty) > 0:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
frappe.listview_settings['Material Request'] = {
|
||||
frappe.listview_settings["Material Request"] = {
|
||||
add_fields: ["material_request_type", "status", "per_ordered", "per_received", "transfer_status"],
|
||||
get_indicator: function(doc) {
|
||||
get_indicator: function (doc) {
|
||||
var precision = frappe.defaults.get_default("float_precision");
|
||||
if (doc.status=="Stopped") {
|
||||
if (doc.status == "Stopped") {
|
||||
return [__("Stopped"), "red", "status,=,Stopped"];
|
||||
} else if (doc.transfer_status && doc.docstatus != 2) {
|
||||
if (doc.transfer_status == "Not Started") {
|
||||
@@ -12,12 +12,16 @@ frappe.listview_settings['Material Request'] = {
|
||||
} else if (doc.transfer_status == "Completed") {
|
||||
return [__("Completed"), "green"];
|
||||
}
|
||||
} else if (doc.docstatus==1 && flt(doc.per_ordered, precision) == 0) {
|
||||
} else if (doc.docstatus == 1 && flt(doc.per_ordered, precision) == 0) {
|
||||
return [__("Pending"), "orange", "per_ordered,=,0"];
|
||||
} else if (doc.docstatus==1 && flt(doc.per_ordered, precision) < 100) {
|
||||
} else if (doc.docstatus == 1 && flt(doc.per_ordered, precision) < 100) {
|
||||
return [__("Partially ordered"), "yellow", "per_ordered,<,100"];
|
||||
} else if (doc.docstatus==1 && flt(doc.per_ordered, precision) == 100) {
|
||||
if (doc.material_request_type == "Purchase" && flt(doc.per_received, precision) < 100 && flt(doc.per_received, precision) > 0) {
|
||||
} else if (doc.docstatus == 1 && flt(doc.per_ordered, precision) == 100) {
|
||||
if (
|
||||
doc.material_request_type == "Purchase" &&
|
||||
flt(doc.per_received, precision) < 100 &&
|
||||
flt(doc.per_received, precision) > 0
|
||||
) {
|
||||
return [__("Partially Received"), "yellow", "per_received,<,100"];
|
||||
} else if (doc.material_request_type == "Purchase" && flt(doc.per_received, precision) == 100) {
|
||||
return [__("Received"), "green", "per_received,=,100"];
|
||||
@@ -33,5 +37,5 @@ frappe.listview_settings['Material Request'] = {
|
||||
return [__("Manufactured"), "green", "per_ordered,=,100"];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -744,9 +744,7 @@ class TestMaterialRequest(FrappeTestCase):
|
||||
self.assertEqual(mr.per_ordered, 100)
|
||||
|
||||
def test_customer_provided_parts_mr(self):
|
||||
create_item(
|
||||
"CUST-0987", is_customer_provided_item=1, customer="_Test Customer", is_purchase_item=0
|
||||
)
|
||||
create_item("CUST-0987", is_customer_provided_item=1, customer="_Test Customer", is_purchase_item=0)
|
||||
existing_requested_qty = self._get_requested_qty("_Test Customer", "_Test Warehouse - _TC")
|
||||
|
||||
mr = make_material_request(item_code="CUST-0987", material_request_type="Customer Provided")
|
||||
@@ -762,6 +760,62 @@ class TestMaterialRequest(FrappeTestCase):
|
||||
self.assertEqual(mr.per_ordered, 100)
|
||||
self.assertEqual(existing_requested_qty, current_requested_qty)
|
||||
|
||||
def test_auto_email_users_with_company_user_permissions(self):
|
||||
from erpnext.stock.reorder_item import get_email_list
|
||||
|
||||
comapnywise_users = {
|
||||
"_Test Company": "test_auto_email_@example.com",
|
||||
"_Test Company 1": "test_auto_email_1@example.com",
|
||||
}
|
||||
|
||||
permissions = []
|
||||
|
||||
for company, user in comapnywise_users.items():
|
||||
if not frappe.db.exists("User", user):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "User",
|
||||
"email": user,
|
||||
"first_name": user,
|
||||
"send_notifications": 0,
|
||||
"enabled": 1,
|
||||
"user_type": "System User",
|
||||
"roles": [{"role": "Purchase Manager"}],
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
if not frappe.db.exists(
|
||||
"User Permission", {"user": user, "allow": "Company", "for_value": company}
|
||||
):
|
||||
perm_doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "User Permission",
|
||||
"user": user,
|
||||
"allow": "Company",
|
||||
"for_value": company,
|
||||
"apply_to_all_doctypes": 1,
|
||||
}
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
permissions.append(perm_doc)
|
||||
|
||||
comapnywise_mr_list = frappe._dict({})
|
||||
mr1 = make_material_request()
|
||||
comapnywise_mr_list.setdefault(mr1.company, []).append(mr1.name)
|
||||
|
||||
mr2 = make_material_request(
|
||||
company="_Test Company 1", warehouse="Stores - _TC1", cost_center="Main - _TC1"
|
||||
)
|
||||
comapnywise_mr_list.setdefault(mr2.company, []).append(mr2.name)
|
||||
|
||||
for company, _mr_list in comapnywise_mr_list.items():
|
||||
emails = get_email_list(company)
|
||||
|
||||
self.assertTrue(comapnywise_users[company] in emails)
|
||||
|
||||
for perm in permissions:
|
||||
perm.delete()
|
||||
|
||||
|
||||
def get_in_transit_warehouse(company):
|
||||
if not frappe.db.exists("Warehouse Type", "Transit"):
|
||||
@@ -772,9 +826,7 @@ def get_in_transit_warehouse(company):
|
||||
}
|
||||
).insert()
|
||||
|
||||
in_transit_warehouse = frappe.db.exists(
|
||||
"Warehouse", {"warehouse_type": "Transit", "company": company}
|
||||
)
|
||||
in_transit_warehouse = frappe.db.exists("Warehouse", {"warehouse_type": "Transit", "company": company})
|
||||
|
||||
if not in_transit_warehouse:
|
||||
in_transit_warehouse = (
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"received_qty",
|
||||
"rate_and_amount_section_break",
|
||||
"rate",
|
||||
"price_list_rate",
|
||||
"col_break3",
|
||||
"amount",
|
||||
"accounting_details_section",
|
||||
@@ -474,13 +475,22 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "WIP Composite Asset",
|
||||
"options": "Asset"
|
||||
},
|
||||
{
|
||||
"fieldname": "price_list_rate",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Price List Rate",
|
||||
"options": "currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-14 18:37:59.599115",
|
||||
"modified": "2024-02-08 16:30:56.137858",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Material Request Item",
|
||||
|
||||
@@ -23,9 +23,7 @@ def make_packing_list(doc):
|
||||
return
|
||||
|
||||
parent_items_price, reset = {}, False
|
||||
set_price_from_children = frappe.db.get_single_value(
|
||||
"Selling Settings", "editable_bundle_item_rates"
|
||||
)
|
||||
set_price_from_children = frappe.db.get_single_value("Selling Settings", "editable_bundle_item_rates")
|
||||
|
||||
stale_packed_items_table = get_indexed_packed_items_table(doc)
|
||||
|
||||
@@ -244,9 +242,7 @@ def get_packed_item_bin_qty(item, warehouse):
|
||||
def get_cancelled_doc_packed_item_details(old_packed_items):
|
||||
prev_doc_packed_items_map = {}
|
||||
for items in old_packed_items:
|
||||
prev_doc_packed_items_map.setdefault((items.item_code, items.parent_item), []).append(
|
||||
items.as_dict()
|
||||
)
|
||||
prev_doc_packed_items_map.setdefault((items.item_code, items.parent_item), []).append(items.as_dict())
|
||||
return prev_doc_packed_items_map
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
@@ -15,8 +14,8 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
|
||||
def create_product_bundle(
|
||||
quantities: Optional[List[int]] = None, warehouse: Optional[str] = None
|
||||
) -> Tuple[str, List[str]]:
|
||||
quantities: list[int] | None = None, warehouse: str | None = None
|
||||
) -> tuple[str, list[str]]:
|
||||
"""Get a new product_bundle for use in tests.
|
||||
|
||||
Create 10x required stock if warehouse is specified.
|
||||
@@ -169,9 +168,7 @@ class TestPackedItem(FrappeTestCase):
|
||||
|
||||
# backdated stock entry
|
||||
for item in self.bundle_items:
|
||||
make_stock_entry(
|
||||
item_code=item, to_warehouse=warehouse, qty=10, rate=200, posting_date=yesterday
|
||||
)
|
||||
make_stock_entry(item_code=item, to_warehouse=warehouse, qty=10, rate=200, posting_date=yesterday)
|
||||
|
||||
# assert correct reposting
|
||||
gles = get_gl_entries(dn.doctype, dn.name)
|
||||
@@ -182,14 +179,15 @@ class TestPackedItem(FrappeTestCase):
|
||||
def assertReturns(self, original, returned):
|
||||
self.assertEqual(len(original), len(returned))
|
||||
|
||||
sort_function = lambda p: (p.parent_item, p.item_code, p.qty)
|
||||
def sort_function(p):
|
||||
return p.parent_item, p.item_code, p.qty
|
||||
|
||||
for sent, returned in zip(
|
||||
sorted(original, key=sort_function), sorted(returned, key=sort_function)
|
||||
for sent_item, returned_item in zip(
|
||||
sorted(original, key=sort_function), sorted(returned, key=sort_function), strict=False
|
||||
):
|
||||
self.assertEqual(sent.item_code, returned.item_code)
|
||||
self.assertEqual(sent.parent_item, returned.parent_item)
|
||||
self.assertEqual(sent.qty, -1 * returned.qty)
|
||||
self.assertEqual(sent_item.item_code, returned_item.item_code)
|
||||
self.assertEqual(sent_item.parent_item, returned_item.parent_item)
|
||||
self.assertEqual(sent_item.qty, -1 * returned_item.qty)
|
||||
|
||||
def test_returning_full_bundles(self):
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Packing Slip', {
|
||||
setup: (frm) => {
|
||||
frm.set_query('delivery_note', () => {
|
||||
return {
|
||||
filters: {
|
||||
docstatus: 0,
|
||||
}
|
||||
}
|
||||
});
|
||||
frappe.ui.form.on("Packing Slip", {
|
||||
setup: (frm) => {
|
||||
frm.set_query("delivery_note", () => {
|
||||
return {
|
||||
filters: {
|
||||
docstatus: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('item_code', 'items', (doc, cdt, cdn) => {
|
||||
if (!doc.delivery_note) {
|
||||
frappe.throw(__('Please select a Delivery Note'));
|
||||
} else {
|
||||
let d = locals[cdt][cdn];
|
||||
return {
|
||||
query: 'erpnext.stock.doctype.packing_slip.packing_slip.item_details',
|
||||
filters: {
|
||||
delivery_note: doc.delivery_note,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
frm.set_query("item_code", "items", (doc, cdt, cdn) => {
|
||||
if (!doc.delivery_note) {
|
||||
frappe.throw(__("Please select a Delivery Note"));
|
||||
} else {
|
||||
let d = locals[cdt][cdn];
|
||||
return {
|
||||
query: "erpnext.stock.doctype.packing_slip.packing_slip.item_details",
|
||||
filters: {
|
||||
delivery_note: doc.delivery_note,
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
refresh: (frm) => {
|
||||
frm.toggle_display('misc_details', frm.doc.amended_from);
|
||||
frm.toggle_display("misc_details", frm.doc.amended_from);
|
||||
},
|
||||
|
||||
delivery_note: (frm) => {
|
||||
frm.set_value('items', null);
|
||||
frm.set_value("items", null);
|
||||
|
||||
if (frm.doc.delivery_note) {
|
||||
erpnext.utils.map_current_doc({
|
||||
method: 'erpnext.stock.doctype.delivery_note.delivery_note.make_packing_slip',
|
||||
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_packing_slip",
|
||||
source_name: frm.doc.delivery_note,
|
||||
target_doc: frm,
|
||||
freeze: true,
|
||||
freeze_message: __('Creating Packing Slip ...'),
|
||||
freeze_message: __("Creating Packing Slip ..."),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ from erpnext.controllers.status_updater import StatusUpdater
|
||||
|
||||
class PackingSlip(StatusUpdater):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super(PackingSlip, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.status_updater = [
|
||||
{
|
||||
"target_dt": "Delivery Note Item",
|
||||
@@ -64,9 +64,7 @@ class PackingSlip(StatusUpdater):
|
||||
"""Validate if case nos overlap. If they do, recommend next case no."""
|
||||
|
||||
if cint(self.from_case_no) <= 0:
|
||||
frappe.throw(
|
||||
_("The 'From Package No.' field must neither be empty nor it's value less than 1.")
|
||||
)
|
||||
frappe.throw(_("The 'From Package No.' field must neither be empty nor it's value less than 1."))
|
||||
elif not self.to_case_no:
|
||||
self.to_case_no = self.from_case_no
|
||||
elif cint(self.to_case_no) < cint(self.from_case_no):
|
||||
@@ -189,9 +187,8 @@ def item_details(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(
|
||||
"""select name, item_name, description from `tabItem`
|
||||
where name in ( select item_code FROM `tabDelivery Note Item`
|
||||
where parent= %s)
|
||||
and %s like "%s" %s
|
||||
limit %s offset %s """
|
||||
% ("%s", searchfield, "%s", get_match_cond(doctype), "%s", "%s"),
|
||||
where parent= {})
|
||||
and {} like "{}" {}
|
||||
limit {} offset {} """.format("%s", searchfield, "%s", get_match_cond(doctype), "%s", "%s"),
|
||||
((filters or {}).get("delivery_note"), "%%%s%%" % txt, page_len, start),
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
@@ -1,61 +1,62 @@
|
||||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Pick List', {
|
||||
frappe.ui.form.on("Pick List", {
|
||||
setup: (frm) => {
|
||||
frm.set_indicator_formatter('item_code',
|
||||
function(doc) { return (doc.stock_qty === 0) ? "red" : "green"; });
|
||||
frm.set_indicator_formatter("item_code", function (doc) {
|
||||
return doc.stock_qty === 0 ? "red" : "green";
|
||||
});
|
||||
|
||||
frm.custom_make_buttons = {
|
||||
'Delivery Note': 'Delivery Note',
|
||||
'Stock Entry': 'Stock Entry',
|
||||
"Delivery Note": "Delivery Note",
|
||||
"Stock Entry": "Stock Entry",
|
||||
};
|
||||
frm.set_query('parent_warehouse', () => {
|
||||
frm.set_query("parent_warehouse", () => {
|
||||
return {
|
||||
filters: {
|
||||
'is_group': 1,
|
||||
'company': frm.doc.company
|
||||
}
|
||||
is_group: 1,
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
frm.set_query('work_order', () => {
|
||||
frm.set_query("work_order", () => {
|
||||
return {
|
||||
query: 'erpnext.stock.doctype.pick_list.pick_list.get_pending_work_orders',
|
||||
query: "erpnext.stock.doctype.pick_list.pick_list.get_pending_work_orders",
|
||||
filters: {
|
||||
'company': frm.doc.company
|
||||
}
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
frm.set_query('material_request', () => {
|
||||
frm.set_query("material_request", () => {
|
||||
return {
|
||||
filters: {
|
||||
'material_request_type': ['=', frm.doc.purpose]
|
||||
}
|
||||
material_request_type: ["=", frm.doc.purpose],
|
||||
},
|
||||
};
|
||||
});
|
||||
frm.set_query('item_code', 'locations', () => {
|
||||
return erpnext.queries.item({ "is_stock_item": 1 });
|
||||
frm.set_query("item_code", "locations", () => {
|
||||
return erpnext.queries.item({ is_stock_item: 1 });
|
||||
});
|
||||
frm.set_query('batch_no', 'locations', (frm, cdt, cdn) => {
|
||||
frm.set_query("batch_no", "locations", (frm, cdt, cdn) => {
|
||||
const row = locals[cdt][cdn];
|
||||
return {
|
||||
query: 'erpnext.controllers.queries.get_batch_no',
|
||||
query: "erpnext.controllers.queries.get_batch_no",
|
||||
filters: {
|
||||
item_code: row.item_code,
|
||||
warehouse: row.warehouse
|
||||
warehouse: row.warehouse,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
set_item_locations:(frm, save) => {
|
||||
set_item_locations: (frm, save) => {
|
||||
if (!(frm.doc.locations && frm.doc.locations.length)) {
|
||||
frappe.msgprint(__('Add items in the Item Locations table'));
|
||||
frappe.msgprint(__("Add items in the Item Locations table"));
|
||||
} else {
|
||||
frappe.call({
|
||||
method: "set_item_locations",
|
||||
doc: frm.doc,
|
||||
args: {
|
||||
"save": save,
|
||||
save: save,
|
||||
},
|
||||
freeze: 1,
|
||||
freeze_message: __("Setting Item Locations..."),
|
||||
@@ -67,155 +68,172 @@ frappe.ui.form.on('Pick List', {
|
||||
frm.events.set_item_locations(frm, false);
|
||||
},
|
||||
refresh: (frm) => {
|
||||
frm.trigger('add_get_items_button');
|
||||
frm.trigger("add_get_items_button");
|
||||
if (frm.doc.docstatus === 1) {
|
||||
frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.target_document_exists', {
|
||||
'pick_list_name': frm.doc.name,
|
||||
'purpose': frm.doc.purpose
|
||||
}).then(target_document_exists => {
|
||||
frm.set_df_property("locations", "allow_on_submit", target_document_exists ? 0 : 1);
|
||||
frappe
|
||||
.xcall("erpnext.stock.doctype.pick_list.pick_list.target_document_exists", {
|
||||
pick_list_name: frm.doc.name,
|
||||
purpose: frm.doc.purpose,
|
||||
})
|
||||
.then((target_document_exists) => {
|
||||
frm.set_df_property("locations", "allow_on_submit", target_document_exists ? 0 : 1);
|
||||
|
||||
if (target_document_exists) return;
|
||||
if (target_document_exists) return;
|
||||
|
||||
frm.add_custom_button(__('Update Current Stock'), () => frm.trigger('update_pick_list_stock'));
|
||||
frm.add_custom_button(__("Update Current Stock"), () =>
|
||||
frm.trigger("update_pick_list_stock")
|
||||
);
|
||||
|
||||
if (frm.doc.purpose === 'Delivery') {
|
||||
frm.add_custom_button(__('Delivery Note'), () => frm.trigger('create_delivery_note'), __('Create'));
|
||||
} else {
|
||||
frm.add_custom_button(__('Stock Entry'), () => frm.trigger('create_stock_entry'), __('Create'));
|
||||
}
|
||||
});
|
||||
if (frm.doc.purpose === "Delivery") {
|
||||
frm.add_custom_button(
|
||||
__("Delivery Note"),
|
||||
() => frm.trigger("create_delivery_note"),
|
||||
__("Create")
|
||||
);
|
||||
} else {
|
||||
frm.add_custom_button(
|
||||
__("Stock Entry"),
|
||||
() => frm.trigger("create_stock_entry"),
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
work_order: (frm) => {
|
||||
frappe.db.get_value('Work Order',
|
||||
frm.doc.work_order,
|
||||
['qty', 'material_transferred_for_manufacturing']
|
||||
).then(data => {
|
||||
let qty_data = data.message;
|
||||
let max = qty_data.qty - qty_data.material_transferred_for_manufacturing;
|
||||
frappe.prompt({
|
||||
fieldtype: 'Float',
|
||||
label: __('Qty of Finished Goods Item'),
|
||||
fieldname: 'qty',
|
||||
description: __('Max: {0}', [max]),
|
||||
default: max
|
||||
}, (data) => {
|
||||
frm.set_value('for_qty', data.qty);
|
||||
if (data.qty > max) {
|
||||
frappe.msgprint(__('Quantity must not be more than {0}', [max]));
|
||||
return;
|
||||
}
|
||||
frm.clear_table('locations');
|
||||
erpnext.utils.map_current_doc({
|
||||
method: 'erpnext.manufacturing.doctype.work_order.work_order.create_pick_list',
|
||||
target: frm,
|
||||
source_name: frm.doc.work_order
|
||||
});
|
||||
}, __('Select Quantity'), __('Get Items'));
|
||||
});
|
||||
frappe.db
|
||||
.get_value("Work Order", frm.doc.work_order, ["qty", "material_transferred_for_manufacturing"])
|
||||
.then((data) => {
|
||||
let qty_data = data.message;
|
||||
let max = qty_data.qty - qty_data.material_transferred_for_manufacturing;
|
||||
frappe.prompt(
|
||||
{
|
||||
fieldtype: "Float",
|
||||
label: __("Qty of Finished Goods Item"),
|
||||
fieldname: "qty",
|
||||
description: __("Max: {0}", [max]),
|
||||
default: max,
|
||||
},
|
||||
(data) => {
|
||||
frm.set_value("for_qty", data.qty);
|
||||
if (data.qty > max) {
|
||||
frappe.msgprint(__("Quantity must not be more than {0}", [max]));
|
||||
return;
|
||||
}
|
||||
frm.clear_table("locations");
|
||||
erpnext.utils.map_current_doc({
|
||||
method: "erpnext.manufacturing.doctype.work_order.work_order.create_pick_list",
|
||||
target: frm,
|
||||
source_name: frm.doc.work_order,
|
||||
});
|
||||
},
|
||||
__("Select Quantity"),
|
||||
__("Get Items")
|
||||
);
|
||||
});
|
||||
},
|
||||
material_request: (frm) => {
|
||||
erpnext.utils.map_current_doc({
|
||||
method: 'erpnext.stock.doctype.material_request.material_request.create_pick_list',
|
||||
method: "erpnext.stock.doctype.material_request.material_request.create_pick_list",
|
||||
target: frm,
|
||||
source_name: frm.doc.material_request
|
||||
source_name: frm.doc.material_request,
|
||||
});
|
||||
},
|
||||
purpose: (frm) => {
|
||||
frm.clear_table('locations');
|
||||
frm.trigger('add_get_items_button');
|
||||
frm.clear_table("locations");
|
||||
frm.trigger("add_get_items_button");
|
||||
},
|
||||
create_delivery_note: (frm) => {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: 'erpnext.stock.doctype.pick_list.pick_list.create_delivery_note',
|
||||
frm: frm
|
||||
method: "erpnext.stock.doctype.pick_list.pick_list.create_delivery_note",
|
||||
frm: frm,
|
||||
});
|
||||
|
||||
},
|
||||
create_stock_entry: (frm) => {
|
||||
frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.create_stock_entry', {
|
||||
'pick_list': frm.doc,
|
||||
}).then(stock_entry => {
|
||||
frappe.model.sync(stock_entry);
|
||||
frappe.set_route("Form", 'Stock Entry', stock_entry.name);
|
||||
});
|
||||
frappe
|
||||
.xcall("erpnext.stock.doctype.pick_list.pick_list.create_stock_entry", {
|
||||
pick_list: frm.doc,
|
||||
})
|
||||
.then((stock_entry) => {
|
||||
frappe.model.sync(stock_entry);
|
||||
frappe.set_route("Form", "Stock Entry", stock_entry.name);
|
||||
});
|
||||
},
|
||||
update_pick_list_stock: (frm) => {
|
||||
frm.events.set_item_locations(frm, true);
|
||||
},
|
||||
add_get_items_button: (frm) => {
|
||||
let purpose = frm.doc.purpose;
|
||||
if (purpose != 'Delivery' || frm.doc.docstatus !== 0) return;
|
||||
if (purpose != "Delivery" || frm.doc.docstatus !== 0) return;
|
||||
let get_query_filters = {
|
||||
docstatus: 1,
|
||||
per_delivered: ['<', 100],
|
||||
status: ['!=', ''],
|
||||
customer: frm.doc.customer
|
||||
per_delivered: ["<", 100],
|
||||
status: ["!=", ""],
|
||||
customer: frm.doc.customer,
|
||||
};
|
||||
frm.get_items_btn = frm.add_custom_button(__('Get Items'), () => {
|
||||
frm.get_items_btn = frm.add_custom_button(__("Get Items"), () => {
|
||||
erpnext.utils.map_current_doc({
|
||||
method: 'erpnext.selling.doctype.sales_order.sales_order.create_pick_list',
|
||||
source_doctype: 'Sales Order',
|
||||
method: "erpnext.selling.doctype.sales_order.sales_order.create_pick_list",
|
||||
source_doctype: "Sales Order",
|
||||
target: frm,
|
||||
setters: {
|
||||
company: frm.doc.company,
|
||||
customer: frm.doc.customer
|
||||
customer: frm.doc.customer,
|
||||
},
|
||||
date_field: 'transaction_date',
|
||||
get_query_filters: get_query_filters
|
||||
date_field: "transaction_date",
|
||||
get_query_filters: get_query_filters,
|
||||
});
|
||||
});
|
||||
},
|
||||
scan_barcode: (frm) => {
|
||||
const opts = {
|
||||
frm,
|
||||
items_table_name: 'locations',
|
||||
qty_field: 'picked_qty',
|
||||
max_qty_field: 'qty',
|
||||
items_table_name: "locations",
|
||||
qty_field: "picked_qty",
|
||||
max_qty_field: "qty",
|
||||
dont_allow_new_row: true,
|
||||
prompt_qty: frm.doc.prompt_qty,
|
||||
serial_no_field: "not_supported", // doesn't make sense for picklist without a separate field.
|
||||
serial_no_field: "not_supported", // doesn't make sense for picklist without a separate field.
|
||||
};
|
||||
const barcode_scanner = new erpnext.utils.BarcodeScanner(opts);
|
||||
barcode_scanner.process_scan();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Pick List Item', {
|
||||
frappe.ui.form.on("Pick List Item", {
|
||||
item_code: (frm, cdt, cdn) => {
|
||||
let row = frappe.get_doc(cdt, cdn);
|
||||
if (row.item_code) {
|
||||
get_item_details(row.item_code).then(data => {
|
||||
frappe.model.set_value(cdt, cdn, 'uom', data.stock_uom);
|
||||
frappe.model.set_value(cdt, cdn, 'stock_uom', data.stock_uom);
|
||||
frappe.model.set_value(cdt, cdn, 'conversion_factor', 1);
|
||||
get_item_details(row.item_code).then((data) => {
|
||||
frappe.model.set_value(cdt, cdn, "uom", data.stock_uom);
|
||||
frappe.model.set_value(cdt, cdn, "stock_uom", data.stock_uom);
|
||||
frappe.model.set_value(cdt, cdn, "conversion_factor", 1);
|
||||
});
|
||||
}
|
||||
},
|
||||
uom: (frm, cdt, cdn) => {
|
||||
let row = frappe.get_doc(cdt, cdn);
|
||||
if (row.uom) {
|
||||
get_item_details(row.item_code, row.uom).then(data => {
|
||||
frappe.model.set_value(cdt, cdn, 'conversion_factor', data.conversion_factor);
|
||||
get_item_details(row.item_code, row.uom).then((data) => {
|
||||
frappe.model.set_value(cdt, cdn, "conversion_factor", data.conversion_factor);
|
||||
});
|
||||
}
|
||||
},
|
||||
qty: (frm, cdt, cdn) => {
|
||||
let row = frappe.get_doc(cdt, cdn);
|
||||
frappe.model.set_value(cdt, cdn, 'stock_qty', row.qty * row.conversion_factor);
|
||||
frappe.model.set_value(cdt, cdn, "stock_qty", row.qty * row.conversion_factor);
|
||||
},
|
||||
conversion_factor: (frm, cdt, cdn) => {
|
||||
let row = frappe.get_doc(cdt, cdn);
|
||||
frappe.model.set_value(cdt, cdn, 'stock_qty', row.qty * row.conversion_factor);
|
||||
}
|
||||
frappe.model.set_value(cdt, cdn, "stock_qty", row.qty * row.conversion_factor);
|
||||
},
|
||||
});
|
||||
|
||||
function get_item_details(item_code, uom=null) {
|
||||
function get_item_details(item_code, uom = null) {
|
||||
if (item_code) {
|
||||
return frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.get_item_details', {
|
||||
return frappe.xcall("erpnext.stock.doctype.pick_list.pick_list.get_item_details", {
|
||||
item_code,
|
||||
uom
|
||||
uom,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"for_qty",
|
||||
"column_break_4",
|
||||
"parent_warehouse",
|
||||
"consider_rejected_warehouses",
|
||||
"get_item_locations",
|
||||
"section_break_6",
|
||||
"scan_barcode",
|
||||
@@ -184,11 +185,18 @@
|
||||
"report_hide": 1,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Enable it if users want to consider rejected materials to dispatch.",
|
||||
"fieldname": "consider_rejected_warehouses",
|
||||
"fieldtype": "Check",
|
||||
"label": "Consider Rejected Warehouses"
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-01-24 10:33:43.244476",
|
||||
"modified": "2024-01-24 17:05:20.317180",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Pick List",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import json
|
||||
from collections import OrderedDict, defaultdict
|
||||
from itertools import groupby
|
||||
from typing import Dict, List
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
@@ -32,6 +31,10 @@ class PickList(Document):
|
||||
self.update_status()
|
||||
self.set_item_locations()
|
||||
|
||||
if self.get("locations"):
|
||||
self.validate_sales_order_percentage()
|
||||
|
||||
def validate_sales_order_percentage(self):
|
||||
# set percentage picked in SO
|
||||
for location in self.get("locations"):
|
||||
if (
|
||||
@@ -184,9 +187,9 @@ class PickList(Document):
|
||||
picked_items_details = self.get_picked_items_details(items)
|
||||
self.item_location_map = frappe._dict()
|
||||
|
||||
from_warehouses = None
|
||||
from_warehouses = [self.parent_warehouse] if self.parent_warehouse else []
|
||||
if self.parent_warehouse:
|
||||
from_warehouses = get_descendants_of("Warehouse", self.parent_warehouse)
|
||||
from_warehouses.extend(get_descendants_of("Warehouse", self.parent_warehouse))
|
||||
|
||||
# Create replica before resetting, to handle empty table on update after submit.
|
||||
locations_replica = self.get("locations")
|
||||
@@ -205,12 +208,11 @@ class PickList(Document):
|
||||
self.item_count_map.get(item_code),
|
||||
self.company,
|
||||
picked_item_details=picked_items_details.get(item_code),
|
||||
consider_rejected_warehouses=self.consider_rejected_warehouses,
|
||||
),
|
||||
)
|
||||
|
||||
locations = get_items_with_location_and_quantity(
|
||||
item_doc, self.item_location_map, self.docstatus
|
||||
)
|
||||
locations = get_items_with_location_and_quantity(item_doc, self.item_location_map, self.docstatus)
|
||||
|
||||
item_doc.idx = None
|
||||
item_doc.name = None
|
||||
@@ -264,12 +266,10 @@ class PickList(Document):
|
||||
item_map = OrderedDict()
|
||||
for item in locations:
|
||||
if not item.item_code:
|
||||
frappe.throw("Row #{0}: Item Code is Mandatory".format(item.idx))
|
||||
frappe.throw(f"Row #{item.idx}: Item Code is Mandatory")
|
||||
if not cint(
|
||||
frappe.get_cached_value("Item", item.item_code, "is_stock_item")
|
||||
) and not frappe.db.exists(
|
||||
"Product Bundle", {"new_item_code": item.item_code, "disabled": 0}
|
||||
):
|
||||
) and not frappe.db.exists("Product Bundle", {"new_item_code": item.item_code, "disabled": 0}):
|
||||
continue
|
||||
item_code = item.item_code
|
||||
reference = item.sales_order_item or item.material_request_item
|
||||
@@ -390,7 +390,7 @@ class PickList(Document):
|
||||
|
||||
return picked_items
|
||||
|
||||
def _get_product_bundles(self) -> Dict[str, str]:
|
||||
def _get_product_bundles(self) -> dict[str, str]:
|
||||
# Dict[so_item_row: item_code]
|
||||
product_bundles = {}
|
||||
for item in self.locations:
|
||||
@@ -403,13 +403,11 @@ class PickList(Document):
|
||||
)
|
||||
return product_bundles
|
||||
|
||||
def _get_product_bundle_qty_map(self, bundles: List[str]) -> Dict[str, Dict[str, float]]:
|
||||
def _get_product_bundle_qty_map(self, bundles: list[str]) -> dict[str, dict[str, float]]:
|
||||
# bundle_item_code: Dict[component, qty]
|
||||
product_bundle_qty_map = {}
|
||||
for bundle_item_code in bundles:
|
||||
bundle = frappe.get_last_doc(
|
||||
"Product Bundle", {"new_item_code": bundle_item_code, "disabled": 0}
|
||||
)
|
||||
bundle = frappe.get_last_doc("Product Bundle", {"new_item_code": bundle_item_code, "disabled": 0})
|
||||
product_bundle_qty_map[bundle_item_code] = {item.item_code: item.qty for item in bundle.items}
|
||||
return product_bundle_qty_map
|
||||
|
||||
@@ -435,7 +433,7 @@ def update_pick_list_status(pick_list):
|
||||
doc.run_method("update_status")
|
||||
|
||||
|
||||
def get_picked_items_qty(items) -> List[Dict]:
|
||||
def get_picked_items_qty(items) -> list[dict]:
|
||||
pi_item = frappe.qb.DocType("Pick List Item")
|
||||
return (
|
||||
frappe.qb.from_(pi_item)
|
||||
@@ -465,17 +463,13 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus)
|
||||
locations = []
|
||||
|
||||
# if stock qty is zero on submitted entry, show positive remaining qty to recalculate in case of restock.
|
||||
remaining_stock_qty = (
|
||||
item_doc.qty if (docstatus == 1 and item_doc.stock_qty == 0) else item_doc.stock_qty
|
||||
)
|
||||
remaining_stock_qty = item_doc.qty if (docstatus == 1 and item_doc.stock_qty == 0) else item_doc.stock_qty
|
||||
|
||||
while flt(remaining_stock_qty) > 0 and available_locations:
|
||||
item_location = available_locations.pop(0)
|
||||
item_location = frappe._dict(item_location)
|
||||
|
||||
stock_qty = (
|
||||
remaining_stock_qty if item_location.qty >= remaining_stock_qty else item_location.qty
|
||||
)
|
||||
stock_qty = remaining_stock_qty if item_location.qty >= remaining_stock_qty else item_location.qty
|
||||
qty = stock_qty / (item_doc.conversion_factor or 1)
|
||||
|
||||
uom_must_be_whole_number = frappe.get_cached_value("UOM", item_doc.uom, "must_be_whole_number")
|
||||
@@ -510,7 +504,7 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus)
|
||||
if item_location.serial_no:
|
||||
# set remaining serial numbers
|
||||
item_location.serial_no = item_location.serial_no[-int(qty_diff) :]
|
||||
available_locations = [item_location] + available_locations
|
||||
available_locations = [item_location, *available_locations]
|
||||
|
||||
# update available locations for the item
|
||||
item_location_map[item_doc.item_code] = available_locations
|
||||
@@ -524,6 +518,7 @@ def get_available_item_locations(
|
||||
company,
|
||||
ignore_validation=False,
|
||||
picked_item_details=None,
|
||||
consider_rejected_warehouses=False,
|
||||
):
|
||||
locations = []
|
||||
total_picked_qty = (
|
||||
@@ -534,19 +529,39 @@ def get_available_item_locations(
|
||||
|
||||
if has_batch_no and has_serial_no:
|
||||
locations = get_available_item_locations_for_serial_and_batched_item(
|
||||
item_code, from_warehouses, required_qty, company, total_picked_qty
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
total_picked_qty,
|
||||
consider_rejected_warehouses=consider_rejected_warehouses,
|
||||
)
|
||||
elif has_serial_no:
|
||||
locations = get_available_item_locations_for_serialized_item(
|
||||
item_code, from_warehouses, required_qty, company, total_picked_qty
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
total_picked_qty,
|
||||
consider_rejected_warehouses=consider_rejected_warehouses,
|
||||
)
|
||||
elif has_batch_no:
|
||||
locations = get_available_item_locations_for_batched_item(
|
||||
item_code, from_warehouses, required_qty, company, total_picked_qty
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
total_picked_qty,
|
||||
consider_rejected_warehouses=consider_rejected_warehouses,
|
||||
)
|
||||
else:
|
||||
locations = get_available_item_locations_for_other_item(
|
||||
item_code, from_warehouses, required_qty, company, total_picked_qty
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
total_picked_qty,
|
||||
consider_rejected_warehouses=consider_rejected_warehouses,
|
||||
)
|
||||
|
||||
total_qty_available = sum(location.get("qty") for location in locations)
|
||||
@@ -597,7 +612,12 @@ def get_available_item_locations(
|
||||
|
||||
|
||||
def get_available_item_locations_for_serialized_item(
|
||||
item_code, from_warehouses, required_qty, company, total_picked_qty=0
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
total_picked_qty=0,
|
||||
consider_rejected_warehouses=False,
|
||||
):
|
||||
sn = frappe.qb.DocType("Serial No")
|
||||
query = (
|
||||
@@ -613,6 +633,10 @@ def get_available_item_locations_for_serialized_item(
|
||||
else:
|
||||
query = query.where(Coalesce(sn.warehouse, "") != "")
|
||||
|
||||
if not consider_rejected_warehouses:
|
||||
if rejected_warehouses := get_rejected_warehouses():
|
||||
query = query.where(sn.warehouse.notin(rejected_warehouses))
|
||||
|
||||
serial_nos = query.run(as_list=True)
|
||||
|
||||
warehouse_serial_nos_map = frappe._dict()
|
||||
@@ -627,7 +651,12 @@ def get_available_item_locations_for_serialized_item(
|
||||
|
||||
|
||||
def get_available_item_locations_for_batched_item(
|
||||
item_code, from_warehouses, required_qty, company, total_picked_qty=0
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
total_picked_qty=0,
|
||||
consider_rejected_warehouses=False,
|
||||
):
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
batch = frappe.qb.DocType("Batch")
|
||||
@@ -653,15 +682,28 @@ def get_available_item_locations_for_batched_item(
|
||||
if from_warehouses:
|
||||
query = query.where(sle.warehouse.isin(from_warehouses))
|
||||
|
||||
if not consider_rejected_warehouses:
|
||||
if rejected_warehouses := get_rejected_warehouses():
|
||||
query = query.where(sle.warehouse.notin(rejected_warehouses))
|
||||
|
||||
return query.run(as_dict=True)
|
||||
|
||||
|
||||
def get_available_item_locations_for_serial_and_batched_item(
|
||||
item_code, from_warehouses, required_qty, company, total_picked_qty=0
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
total_picked_qty=0,
|
||||
consider_rejected_warehouses=False,
|
||||
):
|
||||
# Get batch nos by FIFO
|
||||
locations = get_available_item_locations_for_batched_item(
|
||||
item_code, from_warehouses, required_qty, company
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
consider_rejected_warehouses=consider_rejected_warehouses,
|
||||
)
|
||||
|
||||
if locations:
|
||||
@@ -691,7 +733,12 @@ def get_available_item_locations_for_serial_and_batched_item(
|
||||
|
||||
|
||||
def get_available_item_locations_for_other_item(
|
||||
item_code, from_warehouses, required_qty, company, total_picked_qty=0
|
||||
item_code,
|
||||
from_warehouses,
|
||||
required_qty,
|
||||
company,
|
||||
total_picked_qty=0,
|
||||
consider_rejected_warehouses=False,
|
||||
):
|
||||
bin = frappe.qb.DocType("Bin")
|
||||
query = (
|
||||
@@ -708,6 +755,10 @@ def get_available_item_locations_for_other_item(
|
||||
wh = frappe.qb.DocType("Warehouse")
|
||||
query = query.from_(wh).where((bin.warehouse == wh.name) & (wh.company == company))
|
||||
|
||||
if not consider_rejected_warehouses:
|
||||
if rejected_warehouses := get_rejected_warehouses():
|
||||
query = query.where(bin.warehouse.notin(rejected_warehouses))
|
||||
|
||||
item_locations = query.run(as_dict=True)
|
||||
|
||||
return item_locations
|
||||
@@ -768,8 +819,7 @@ def create_dn_with_so(sales_dict, pick_list):
|
||||
"name": "so_detail",
|
||||
"parent": "against_sales_order",
|
||||
},
|
||||
"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty)
|
||||
and doc.delivered_by_supplier != 1,
|
||||
"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier != 1,
|
||||
}
|
||||
|
||||
for customer in sales_dict:
|
||||
@@ -790,7 +840,6 @@ def create_dn_with_so(sales_dict, pick_list):
|
||||
|
||||
|
||||
def map_pl_locations(pick_list, item_mapper, delivery_note, sales_order=None):
|
||||
|
||||
for location in pick_list.locations:
|
||||
if location.sales_order != sales_order or location.product_bundle_item:
|
||||
continue
|
||||
@@ -821,9 +870,7 @@ def map_pl_locations(pick_list, item_mapper, delivery_note, sales_order=None):
|
||||
delivery_note.customer = frappe.get_value("Sales Order", sales_order, "customer")
|
||||
|
||||
|
||||
def add_product_bundles_to_delivery_note(
|
||||
pick_list: "PickList", delivery_note, item_mapper
|
||||
) -> None:
|
||||
def add_product_bundles_to_delivery_note(pick_list: "PickList", delivery_note, item_mapper) -> None:
|
||||
"""Add product bundles found in pick list to delivery note.
|
||||
|
||||
When mapping pick list items, the bundle item itself isn't part of the
|
||||
@@ -901,7 +948,7 @@ def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filte
|
||||
& (wo.qty > wo.material_transferred_for_manufacturing)
|
||||
& (wo.docstatus == 1)
|
||||
& (wo.company == filters.get("company"))
|
||||
& (wo.name.like("%{0}%".format(txt)))
|
||||
& (wo.name.like(f"%{txt}%"))
|
||||
)
|
||||
.orderby(Case().when(Locate(txt, wo.name) > 0, Locate(txt, wo.name)).else_(99999))
|
||||
.orderby(wo.name)
|
||||
@@ -968,9 +1015,7 @@ def update_stock_entry_based_on_work_order(pick_list, stock_entry):
|
||||
stock_entry.use_multi_level_bom = work_order.use_multi_level_bom
|
||||
stock_entry.fg_completed_qty = pick_list.for_qty
|
||||
if work_order.bom_no:
|
||||
stock_entry.inspection_required = frappe.db.get_value(
|
||||
"BOM", work_order.bom_no, "inspection_required"
|
||||
)
|
||||
stock_entry.inspection_required = frappe.db.get_value("BOM", work_order.bom_no, "inspection_required")
|
||||
|
||||
is_wip_warehouse_group = frappe.db.get_value("Warehouse", work_order.wip_warehouse, "is_group")
|
||||
if not (is_wip_warehouse_group and work_order.skip_transfer):
|
||||
@@ -1028,3 +1073,15 @@ def update_common_item_properties(item, location):
|
||||
item.serial_no = location.serial_no
|
||||
item.batch_no = location.batch_no
|
||||
item.material_request_item = location.material_request_item
|
||||
|
||||
|
||||
def get_rejected_warehouses():
|
||||
if not hasattr(frappe.local, "rejected_warehouses"):
|
||||
frappe.local.rejected_warehouses = []
|
||||
|
||||
if not frappe.local.rejected_warehouses:
|
||||
frappe.local.rejected_warehouses = frappe.get_all(
|
||||
"Warehouse", filters={"is_rejected_warehouse": 1}, pluck="name"
|
||||
)
|
||||
|
||||
return frappe.local.rejected_warehouses
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.listview_settings['Pick List'] = {
|
||||
frappe.listview_settings["Pick List"] = {
|
||||
get_indicator: function (doc) {
|
||||
const status_colors = {
|
||||
"Draft": "grey",
|
||||
"Open": "orange",
|
||||
"Completed": "green",
|
||||
"Cancelled": "red",
|
||||
Draft: "grey",
|
||||
Open: "orange",
|
||||
Completed: "green",
|
||||
Cancelled: "red",
|
||||
};
|
||||
return [__(doc.status), status_colors[doc.status], "status,=," + doc.status];
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -139,7 +139,6 @@ class TestPickList(FrappeTestCase):
|
||||
self.assertEqual(pick_list.locations[1].qty, 10)
|
||||
|
||||
def test_pick_list_shows_serial_no_for_serialized_item(self):
|
||||
|
||||
stock_reconciliation = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Stock Reconciliation",
|
||||
@@ -274,7 +273,6 @@ class TestPickList(FrappeTestCase):
|
||||
pr2.cancel()
|
||||
|
||||
def test_pick_list_for_items_from_multiple_sales_orders(self):
|
||||
|
||||
item_code = make_item().name
|
||||
try:
|
||||
frappe.get_doc(
|
||||
@@ -418,9 +416,7 @@ class TestPickList(FrappeTestCase):
|
||||
|
||||
self.assertEqual(pick_list.locations[0].qty, delivery_note.items[0].qty)
|
||||
self.assertEqual(pick_list.locations[1].qty, delivery_note.items[1].qty)
|
||||
self.assertEqual(
|
||||
sales_order.items[0].conversion_factor, delivery_note.items[0].conversion_factor
|
||||
)
|
||||
self.assertEqual(sales_order.items[0].conversion_factor, delivery_note.items[0].conversion_factor)
|
||||
|
||||
pick_list.cancel()
|
||||
sales_order.cancel()
|
||||
@@ -469,7 +465,7 @@ class TestPickList(FrappeTestCase):
|
||||
_dict(item_code="A", warehouse="X", qty=8, picked_qty=3),
|
||||
_dict(item_code="B", warehouse="Y", qty=6, picked_qty=4),
|
||||
]
|
||||
for expected_item, created_item in zip(expected_items, pl.locations):
|
||||
for expected_item, created_item in zip(expected_items, pl.locations, strict=False):
|
||||
_compare_dicts(expected_item, created_item)
|
||||
|
||||
def test_multiple_dn_creation(self):
|
||||
@@ -579,9 +575,7 @@ class TestPickList(FrappeTestCase):
|
||||
pick_list_1.set_item_locations()
|
||||
pick_list_1.submit()
|
||||
create_delivery_note(pick_list_1.name)
|
||||
for dn in frappe.get_all(
|
||||
"Delivery Note", filters={"pick_list": pick_list_1.name}, fields={"name"}
|
||||
):
|
||||
for dn in frappe.get_all("Delivery Note", filters={"pick_list": pick_list_1.name}, fields={"name"}):
|
||||
for dn_item in frappe.get_doc("Delivery Note", dn.name).get("items"):
|
||||
if dn_item.item_code == "_Test Item":
|
||||
self.assertEqual(dn_item.qty, 1)
|
||||
@@ -609,7 +603,7 @@ class TestPickList(FrappeTestCase):
|
||||
|
||||
quantities = [5, 2]
|
||||
bundle, components = create_product_bundle(quantities, warehouse=warehouse)
|
||||
bundle_items = dict(zip(components, quantities))
|
||||
bundle_items = dict(zip(components, quantities, strict=False))
|
||||
|
||||
so = make_sales_order(item_code=bundle, qty=3, rate=42)
|
||||
|
||||
@@ -715,7 +709,7 @@ class TestPickList(FrappeTestCase):
|
||||
|
||||
for item in items:
|
||||
for warehouse in warehouses:
|
||||
se = make_stock_entry(
|
||||
make_stock_entry(
|
||||
item=item.get("item_code"),
|
||||
to_warehouse=warehouse,
|
||||
qty=5,
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.ui.form.on("Price List", {
|
||||
refresh: function(frm) {
|
||||
refresh: function (frm) {
|
||||
let me = this;
|
||||
frm.add_custom_button(__("Add / Edit Prices"), function() {
|
||||
frappe.route_options = {
|
||||
"price_list": frm.doc.name
|
||||
};
|
||||
frappe.set_route("Report", "Item Price");
|
||||
}, "fa fa-money");
|
||||
}
|
||||
frm.add_custom_button(
|
||||
__("Add / Edit Prices"),
|
||||
function () {
|
||||
frappe.route_options = {
|
||||
price_list: frm.doc.name,
|
||||
};
|
||||
frappe.set_route("Report", "Item Price");
|
||||
},
|
||||
"fa fa-money"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -78,6 +78,20 @@ frappe.ui.form.on("Purchase Receipt", {
|
||||
}, __('Create'));
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus === 0) {
|
||||
if (!frm.doc.is_return) {
|
||||
frappe.db.get_single_value("Buying Settings", "maintain_same_rate").then((value) => {
|
||||
if (value) {
|
||||
frm.doc.items.forEach((item) => {
|
||||
frm.fields_dict.items.grid.update_docfield_property(
|
||||
"rate", "read_only", (item.purchase_order && item.purchase_order_item)
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
frm.events.add_custom_buttons(frm);
|
||||
},
|
||||
|
||||
|
||||
@@ -649,7 +649,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "Long Text",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Taxes and Charges Calculation",
|
||||
"no_copy": 1,
|
||||
"oldfieldtype": "HTML",
|
||||
@@ -1242,7 +1242,7 @@
|
||||
"idx": 261,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-18 17:26:41.279663",
|
||||
"modified": "2024-03-20 16:05:31.713453",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt",
|
||||
|
||||
@@ -14,6 +14,7 @@ import erpnext
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
||||
from erpnext.controllers.accounts_controller import merge_taxes
|
||||
from erpnext.controllers.buying_controller import BuyingController
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_transaction
|
||||
|
||||
@@ -22,7 +23,7 @@ form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
|
||||
|
||||
class PurchaseReceipt(BuyingController):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PurchaseReceipt, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.status_updater = [
|
||||
{
|
||||
"target_dt": "Purchase Order Item",
|
||||
@@ -115,7 +116,7 @@ class PurchaseReceipt(BuyingController):
|
||||
|
||||
def validate(self):
|
||||
self.validate_posting_time()
|
||||
super(PurchaseReceipt, self).validate()
|
||||
super().validate()
|
||||
|
||||
if self._action == "submit":
|
||||
self.make_batches("warehouse")
|
||||
@@ -125,8 +126,7 @@ class PurchaseReceipt(BuyingController):
|
||||
self.po_required()
|
||||
self.validate_items_quality_inspection()
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_uom_is_integer("uom", ["qty", "received_qty"])
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
self.validate_uom_is_integer()
|
||||
self.validate_cwip_accounts()
|
||||
self.validate_provisional_expense_account()
|
||||
|
||||
@@ -140,6 +140,10 @@ class PurchaseReceipt(BuyingController):
|
||||
self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse")
|
||||
self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
|
||||
|
||||
def validate_uom_is_integer(self):
|
||||
super().validate_uom_is_integer("uom", ["qty", "received_qty"], "Purchase Receipt Item")
|
||||
super().validate_uom_is_integer("stock_uom", "stock_qty", "Purchase Receipt Item")
|
||||
|
||||
def validate_cwip_accounts(self):
|
||||
for item in self.get("items"):
|
||||
if item.is_fixed_asset and is_cwip_accounting_enabled(item.asset_category):
|
||||
@@ -147,15 +151,15 @@ class PurchaseReceipt(BuyingController):
|
||||
# Improves UX by not giving messages of "Assets Created" before throwing error of not finding arbnb account
|
||||
self.get_company_default("asset_received_but_not_billed")
|
||||
get_asset_account(
|
||||
"capital_work_in_progress_account", asset_category=item.asset_category, company=self.company
|
||||
"capital_work_in_progress_account",
|
||||
asset_category=item.asset_category,
|
||||
company=self.company,
|
||||
)
|
||||
break
|
||||
|
||||
def validate_provisional_expense_account(self):
|
||||
provisional_accounting_for_non_stock_items = cint(
|
||||
frappe.db.get_value(
|
||||
"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
|
||||
)
|
||||
frappe.db.get_value("Company", self.company, "enable_provisional_accounting_for_non_stock_items")
|
||||
)
|
||||
|
||||
if not provisional_accounting_for_non_stock_items:
|
||||
@@ -167,7 +171,7 @@ class PurchaseReceipt(BuyingController):
|
||||
item.provisional_expense_account = default_provisional_account
|
||||
|
||||
def validate_with_previous_doc(self):
|
||||
super(PurchaseReceipt, self).validate_with_previous_doc(
|
||||
super().validate_with_previous_doc(
|
||||
{
|
||||
"Purchase Order": {
|
||||
"ref_dn_field": "purchase_order",
|
||||
@@ -228,24 +232,20 @@ class PurchaseReceipt(BuyingController):
|
||||
return qty and flt(qty[0][0]) or 0.0
|
||||
|
||||
def get_po_qty_and_warehouse(self, po_detail):
|
||||
po_qty, po_warehouse = frappe.db.get_value(
|
||||
"Purchase Order Item", po_detail, ["qty", "warehouse"]
|
||||
)
|
||||
po_qty, po_warehouse = frappe.db.get_value("Purchase Order Item", po_detail, ["qty", "warehouse"])
|
||||
return po_qty, po_warehouse
|
||||
|
||||
# Check for Closed status
|
||||
def check_on_hold_or_closed_status(self):
|
||||
check_list = []
|
||||
for d in self.get("items"):
|
||||
if (
|
||||
d.meta.get_field("purchase_order") and d.purchase_order and d.purchase_order not in check_list
|
||||
):
|
||||
if d.meta.get_field("purchase_order") and d.purchase_order and d.purchase_order not in check_list:
|
||||
check_list.append(d.purchase_order)
|
||||
check_on_hold_or_closed_status("Purchase Order", d.purchase_order)
|
||||
|
||||
# on submit
|
||||
def on_submit(self):
|
||||
super(PurchaseReceipt, self).on_submit()
|
||||
super().on_submit()
|
||||
|
||||
# Check for Approving Authority
|
||||
frappe.get_doc("Authorization Control").validate_approving_authority(
|
||||
@@ -282,7 +282,7 @@ class PurchaseReceipt(BuyingController):
|
||||
frappe.throw(_("Purchase Invoice {0} is already submitted").format(self.submit_rv[0][0]))
|
||||
|
||||
def on_cancel(self):
|
||||
super(PurchaseReceipt, self).on_cancel()
|
||||
super().on_cancel()
|
||||
|
||||
self.check_on_hold_or_closed_status()
|
||||
# Check if Purchase Invoice has been submitted against current Purchase Order
|
||||
@@ -324,9 +324,7 @@ class PurchaseReceipt(BuyingController):
|
||||
)
|
||||
|
||||
provisional_accounting_for_non_stock_items = cint(
|
||||
frappe.db.get_value(
|
||||
"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
|
||||
)
|
||||
frappe.db.get_value("Company", self.company, "enable_provisional_accounting_for_non_stock_items")
|
||||
)
|
||||
|
||||
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
|
||||
@@ -395,7 +393,6 @@ class PurchaseReceipt(BuyingController):
|
||||
and self.conversion_rate != exchange_rate_map[item.purchase_invoice]
|
||||
and item.net_rate == net_rate_map[item.purchase_invoice_item]
|
||||
):
|
||||
|
||||
discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * (
|
||||
exchange_rate_map[item.purchase_invoice] - self.conversion_rate
|
||||
)
|
||||
@@ -572,17 +569,17 @@ class PurchaseReceipt(BuyingController):
|
||||
)
|
||||
|
||||
stock_value_diff = (
|
||||
flt(d.base_net_amount)
|
||||
+ flt(d.item_tax_amount / self.conversion_rate)
|
||||
+ flt(d.landed_cost_voucher_amount)
|
||||
flt(d.base_net_amount) + flt(d.item_tax_amount) + flt(d.landed_cost_voucher_amount)
|
||||
)
|
||||
elif warehouse_account.get(d.warehouse):
|
||||
stock_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse)
|
||||
stock_asset_account_name = warehouse_account[d.warehouse]["account"]
|
||||
supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account")
|
||||
supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get(
|
||||
"account_currency"
|
||||
supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get(
|
||||
"account"
|
||||
)
|
||||
supplier_warehouse_account_currency = warehouse_account.get(
|
||||
self.supplier_warehouse, {}
|
||||
).get("account_currency")
|
||||
|
||||
# If PR is sub-contracted and fg item rate is zero
|
||||
# in that case if account for source and target warehouse are same,
|
||||
@@ -606,7 +603,7 @@ class PurchaseReceipt(BuyingController):
|
||||
):
|
||||
warehouse_with_no_account.append(d.warehouse or d.rejected_warehouse)
|
||||
|
||||
if d.is_fixed_asset:
|
||||
if d.is_fixed_asset and d.landed_cost_voucher_amount:
|
||||
self.update_assets(d, d.valuation_rate)
|
||||
|
||||
if warehouse_with_no_account:
|
||||
@@ -617,16 +614,19 @@ class PurchaseReceipt(BuyingController):
|
||||
)
|
||||
|
||||
def add_provisional_gl_entry(
|
||||
self, item, gl_entries, posting_date, provisional_account, reverse=0
|
||||
self, item, gl_entries, posting_date, provisional_account, reverse=0, item_amount=None
|
||||
):
|
||||
credit_currency = get_account_currency(provisional_account)
|
||||
expense_account = item.expense_account
|
||||
debit_currency = get_account_currency(item.expense_account)
|
||||
remarks = self.get("remarks") or _("Accounting Entry for Service")
|
||||
multiplication_factor = 1
|
||||
amount = item.base_amount
|
||||
|
||||
if reverse:
|
||||
multiplication_factor = -1
|
||||
# Post reverse entry for previously posted amount
|
||||
amount = item_amount
|
||||
expense_account = frappe.db.get_value(
|
||||
"Purchase Receipt Item", {"name": item.get("pr_detail")}, ["expense_account"]
|
||||
)
|
||||
@@ -636,7 +636,7 @@ class PurchaseReceipt(BuyingController):
|
||||
account=provisional_account,
|
||||
cost_center=item.cost_center,
|
||||
debit=0.0,
|
||||
credit=multiplication_factor * item.base_amount,
|
||||
credit=multiplication_factor * amount,
|
||||
remarks=remarks,
|
||||
against_account=expense_account,
|
||||
account_currency=credit_currency,
|
||||
@@ -650,7 +650,7 @@ class PurchaseReceipt(BuyingController):
|
||||
gl_entries=gl_entries,
|
||||
account=expense_account,
|
||||
cost_center=item.cost_center,
|
||||
debit=multiplication_factor * item.base_amount,
|
||||
debit=multiplication_factor * amount,
|
||||
credit=0.0,
|
||||
remarks=remarks,
|
||||
against_account=provisional_account,
|
||||
@@ -738,11 +738,14 @@ class PurchaseReceipt(BuyingController):
|
||||
)
|
||||
|
||||
for asset in assets:
|
||||
purchase_amount = flt(valuation_rate) * asset.asset_quantity
|
||||
frappe.db.set_value(
|
||||
"Asset", asset.name, "gross_purchase_amount", flt(valuation_rate) * asset.asset_quantity
|
||||
)
|
||||
frappe.db.set_value(
|
||||
"Asset", asset.name, "purchase_receipt_amount", flt(valuation_rate) * asset.asset_quantity
|
||||
"Asset",
|
||||
asset.name,
|
||||
{
|
||||
"gross_purchase_amount": purchase_amount,
|
||||
"purchase_receipt_amount": purchase_amount,
|
||||
},
|
||||
)
|
||||
|
||||
def update_status(self, status):
|
||||
@@ -971,7 +974,7 @@ def get_item_wise_returned_qty(pr_doc):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_invoice(source_name, target_doc=None):
|
||||
def make_purchase_invoice(source_name, target_doc=None, args=None):
|
||||
from erpnext.accounts.party import get_payment_terms_template
|
||||
|
||||
doc = frappe.get_doc("Purchase Receipt", source_name)
|
||||
@@ -983,19 +986,19 @@ def make_purchase_invoice(source_name, target_doc=None):
|
||||
frappe.throw(_("All items have already been Invoiced/Returned"))
|
||||
|
||||
doc = frappe.get_doc(target)
|
||||
doc.payment_terms_template = get_payment_terms_template(
|
||||
source.supplier, "Supplier", source.company
|
||||
)
|
||||
doc.payment_terms_template = get_payment_terms_template(source.supplier, "Supplier", source.company)
|
||||
doc.run_method("onload")
|
||||
doc.run_method("set_missing_values")
|
||||
|
||||
if args and args.get("merge_taxes"):
|
||||
merge_taxes(source.get("taxes") or [], doc)
|
||||
|
||||
doc.run_method("calculate_taxes_and_totals")
|
||||
doc.set_payment_schedule()
|
||||
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.qty, returned_qty = get_pending_qty(source_doc)
|
||||
if frappe.db.get_single_value(
|
||||
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
|
||||
):
|
||||
if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"):
|
||||
target_doc.rejected_qty = 0
|
||||
target_doc.stock_qty = flt(target_doc.qty) * flt(
|
||||
target_doc.conversion_factor, target_doc.precision("conversion_factor")
|
||||
@@ -1004,9 +1007,7 @@ def make_purchase_invoice(source_name, target_doc=None):
|
||||
|
||||
def get_pending_qty(item_row):
|
||||
qty = item_row.qty
|
||||
if frappe.db.get_single_value(
|
||||
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
|
||||
):
|
||||
if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"):
|
||||
qty = item_row.received_qty
|
||||
pending_qty = qty - invoiced_qty_map.get(item_row.name, 0)
|
||||
returned_qty = flt(returned_qty_map.get(item_row.name, 0))
|
||||
@@ -1039,6 +1040,7 @@ def make_purchase_invoice(source_name, target_doc=None):
|
||||
"field_map": {
|
||||
"name": "pr_detail",
|
||||
"parent": "purchase_receipt",
|
||||
"qty": "received_qty",
|
||||
"purchase_order_item": "po_detail",
|
||||
"purchase_order": "purchase_order",
|
||||
"is_fixed_asset": "is_fixed_asset",
|
||||
@@ -1051,7 +1053,11 @@ def make_purchase_invoice(source_name, target_doc=None):
|
||||
if not doc.get("is_return")
|
||||
else get_pending_qty(d)[0] > 0,
|
||||
},
|
||||
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "add_if_empty": True},
|
||||
"Purchase Taxes and Charges": {
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
"add_if_empty": True,
|
||||
"ignore": args.get("merge_taxes") if args else 0,
|
||||
},
|
||||
},
|
||||
target_doc,
|
||||
set_missing_values,
|
||||
@@ -1157,16 +1163,16 @@ def get_item_account_wise_additional_cost(purchase_document):
|
||||
for lcv in landed_cost_vouchers:
|
||||
landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", lcv.parent)
|
||||
|
||||
based_on_field = None
|
||||
# Use amount field for total item cost for manually cost distributed LCVs
|
||||
if landed_cost_voucher_doc.distribute_charges_based_on == "Distribute Manually":
|
||||
based_on_field = "amount"
|
||||
else:
|
||||
if landed_cost_voucher_doc.distribute_charges_based_on != "Distribute Manually":
|
||||
based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on)
|
||||
|
||||
total_item_cost = 0
|
||||
|
||||
for item in landed_cost_voucher_doc.items:
|
||||
total_item_cost += item.get(based_on_field)
|
||||
if based_on_field:
|
||||
for item in landed_cost_voucher_doc.items:
|
||||
total_item_cost += item.get(based_on_field)
|
||||
|
||||
for item in landed_cost_voucher_doc.items:
|
||||
if item.receipt_document == purchase_document:
|
||||
@@ -1179,15 +1185,11 @@ def get_item_account_wise_additional_cost(purchase_document):
|
||||
if total_item_cost > 0:
|
||||
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
|
||||
account.expense_account
|
||||
]["amount"] += (
|
||||
account.amount * item.get(based_on_field) / total_item_cost
|
||||
)
|
||||
]["amount"] += account.amount * item.get(based_on_field) / total_item_cost
|
||||
|
||||
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
|
||||
account.expense_account
|
||||
]["base_amount"] += (
|
||||
account.base_amount * item.get(based_on_field) / total_item_cost
|
||||
)
|
||||
]["base_amount"] += account.base_amount * item.get(based_on_field) / total_item_cost
|
||||
else:
|
||||
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
|
||||
account.expense_account
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
frappe.listview_settings['Purchase Receipt'] = {
|
||||
add_fields: ["supplier", "supplier_name", "base_grand_total", "is_subcontracted",
|
||||
"transporter_name", "is_return", "status", "per_billed", "currency"],
|
||||
get_indicator: function(doc) {
|
||||
if(cint(doc.is_return)==1) {
|
||||
frappe.listview_settings["Purchase Receipt"] = {
|
||||
add_fields: [
|
||||
"supplier",
|
||||
"supplier_name",
|
||||
"base_grand_total",
|
||||
"is_subcontracted",
|
||||
"transporter_name",
|
||||
"is_return",
|
||||
"status",
|
||||
"per_billed",
|
||||
"currency",
|
||||
],
|
||||
get_indicator: function (doc) {
|
||||
if (cint(doc.is_return) == 1) {
|
||||
return [__("Return"), "gray", "is_return,=,Yes"];
|
||||
} else if (doc.status === "Closed") {
|
||||
return [__("Closed"), "green", "status,=,Closed"];
|
||||
@@ -15,11 +24,9 @@ frappe.listview_settings['Purchase Receipt'] = {
|
||||
}
|
||||
},
|
||||
|
||||
onload: function(listview) {
|
||||
|
||||
listview.page.add_action_item(__("Purchase Invoice"), ()=>{
|
||||
onload: function (listview) {
|
||||
listview.page.add_action_item(__("Purchase Invoice"), () => {
|
||||
erpnext.bulk_transaction_processing.create(listview, "Purchase Receipt", "Purchase Invoice");
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, cint, cstr, flt, today
|
||||
from frappe.utils import add_days, cint, cstr, flt, nowtime, today
|
||||
from pypika import functions as fn
|
||||
|
||||
import erpnext
|
||||
@@ -11,9 +11,8 @@ from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||
from erpnext.controllers.buying_controller import QtyMismatchError
|
||||
from erpnext.stock.doctype.item.test_item import create_item, make_item
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
|
||||
from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction
|
||||
|
||||
|
||||
class TestPurchaseReceipt(FrappeTestCase):
|
||||
@@ -38,7 +37,6 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
pr.delete()
|
||||
|
||||
def test_reverse_purchase_receipt_sle(self):
|
||||
|
||||
pr = make_purchase_receipt(qty=0.5, item_code="_Test Item Home Desktop 200")
|
||||
|
||||
sl_entry = frappe.db.get_all(
|
||||
@@ -125,9 +123,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
pi.delete() # draft PI
|
||||
pr.cancel()
|
||||
frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", old_template_in_supplier)
|
||||
frappe.get_doc(
|
||||
"Payment Terms Template", "_Test Payment Terms Template For Purchase Invoice"
|
||||
).delete()
|
||||
frappe.get_doc("Payment Terms Template", "_Test Payment Terms Template For Purchase Invoice").delete()
|
||||
|
||||
def test_purchase_receipt_no_gl_entry(self):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
@@ -194,87 +190,8 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
batch_no = pr.items[0].batch_no
|
||||
pr.cancel()
|
||||
|
||||
self.assertFalse(frappe.db.get_value("Batch", {"item": item.name, "reference_name": pr.name}))
|
||||
self.assertFalse(frappe.db.get_all("Serial No", {"batch_no": batch_no}))
|
||||
|
||||
def test_duplicate_serial_nos(self):
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
|
||||
item = frappe.db.exists("Item", {"item_name": "Test Serialized Item 123"})
|
||||
if not item:
|
||||
item = create_item("Test Serialized Item 123")
|
||||
item.has_serial_no = 1
|
||||
item.serial_no_series = "TSI123-.####"
|
||||
item.save()
|
||||
else:
|
||||
item = frappe.get_doc("Item", {"item_name": "Test Serialized Item 123"})
|
||||
|
||||
# First make purchase receipt
|
||||
pr = make_purchase_receipt(item_code=item.name, qty=2, rate=500)
|
||||
pr.load_from_db()
|
||||
|
||||
serial_nos = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "item_code": item.name},
|
||||
"serial_no",
|
||||
)
|
||||
|
||||
serial_nos = get_serial_nos(serial_nos)
|
||||
|
||||
self.assertEquals(get_serial_nos(pr.items[0].serial_no), serial_nos)
|
||||
|
||||
# Then tried to receive same serial nos in difference company
|
||||
pr_different_company = make_purchase_receipt(
|
||||
item_code=item.name,
|
||||
qty=2,
|
||||
rate=500,
|
||||
serial_no="\n".join(serial_nos),
|
||||
company="_Test Company 1",
|
||||
do_not_submit=True,
|
||||
warehouse="Stores - _TC1",
|
||||
)
|
||||
|
||||
self.assertRaises(SerialNoDuplicateError, pr_different_company.submit)
|
||||
|
||||
# Then made delivery note to remove the serial nos from stock
|
||||
dn = create_delivery_note(item_code=item.name, qty=2, rate=1500, serial_no="\n".join(serial_nos))
|
||||
dn.load_from_db()
|
||||
self.assertEquals(get_serial_nos(dn.items[0].serial_no), serial_nos)
|
||||
|
||||
posting_date = add_days(today(), -3)
|
||||
|
||||
# Try to receive same serial nos again in the same company with backdated.
|
||||
pr1 = make_purchase_receipt(
|
||||
item_code=item.name,
|
||||
qty=2,
|
||||
rate=500,
|
||||
posting_date=posting_date,
|
||||
serial_no="\n".join(serial_nos),
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
self.assertRaises(SerialNoExistsInFutureTransaction, pr1.submit)
|
||||
|
||||
# Try to receive same serial nos with different company with backdated.
|
||||
pr2 = make_purchase_receipt(
|
||||
item_code=item.name,
|
||||
qty=2,
|
||||
rate=500,
|
||||
posting_date=posting_date,
|
||||
serial_no="\n".join(serial_nos),
|
||||
company="_Test Company 1",
|
||||
do_not_submit=True,
|
||||
warehouse="Stores - _TC1",
|
||||
)
|
||||
|
||||
self.assertRaises(SerialNoExistsInFutureTransaction, pr2.submit)
|
||||
|
||||
# Receive the same serial nos after the delivery note posting date and time
|
||||
make_purchase_receipt(item_code=item.name, qty=2, rate=500, serial_no="\n".join(serial_nos))
|
||||
|
||||
# Raise the error for backdated deliver note entry cancel
|
||||
self.assertRaises(SerialNoExistsInFutureTransaction, dn.cancel)
|
||||
|
||||
def test_purchase_receipt_gl_entry(self):
|
||||
pr = make_purchase_receipt(
|
||||
company="_Test Company with perpetual inventory",
|
||||
@@ -353,7 +270,8 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
self.assertEqual(len(rejected_serial_nos), 2)
|
||||
for serial_no in rejected_serial_nos:
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Serial No", serial_no, "warehouse"), pr.get("items")[0].rejected_warehouse
|
||||
frappe.db.get_value("Serial No", serial_no, "warehouse"),
|
||||
pr.get("items")[0].rejected_warehouse,
|
||||
)
|
||||
|
||||
pr.cancel()
|
||||
@@ -693,7 +611,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
|
||||
item_code = "Test Manual Created Serial No"
|
||||
if not frappe.db.exists("Item", item_code):
|
||||
item = make_item(item_code, dict(has_serial_no=1))
|
||||
make_item(item_code, dict(has_serial_no=1))
|
||||
|
||||
serial_no = "12903812901"
|
||||
pr_doc = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no)
|
||||
@@ -839,7 +757,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
"Stock Received But Not Billed - TCP1": {"cost_center": cost_center},
|
||||
stock_in_hand_account: {"cost_center": cost_center},
|
||||
}
|
||||
for i, gle in enumerate(gl_entries):
|
||||
for _i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
|
||||
|
||||
pr.cancel()
|
||||
@@ -864,7 +782,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
"Stock Received But Not Billed - TCP1": {"cost_center": cost_center},
|
||||
stock_in_hand_account: {"cost_center": cost_center},
|
||||
}
|
||||
for i, gle in enumerate(gl_entries):
|
||||
for _i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
|
||||
|
||||
pr.cancel()
|
||||
@@ -1125,9 +1043,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
pr.submit()
|
||||
|
||||
# Get exchnage gain and loss account
|
||||
exchange_gain_loss_account = frappe.db.get_value(
|
||||
"Company", pr.company, "exchange_gain_loss_account"
|
||||
)
|
||||
exchange_gain_loss_account = frappe.db.get_value("Company", pr.company, "exchange_gain_loss_account")
|
||||
|
||||
# fetching the latest GL Entry with exchange gain and loss account account
|
||||
amount = frappe.db.get_value(
|
||||
@@ -1184,9 +1100,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
account = "Stock Received But Not Billed - TCP1"
|
||||
|
||||
make_item(item_code)
|
||||
se = make_stock_entry(
|
||||
item_code=item_code, from_warehouse=warehouse, qty=50, do_not_save=True, rate=0
|
||||
)
|
||||
se = make_stock_entry(item_code=item_code, from_warehouse=warehouse, qty=50, do_not_save=True, rate=0)
|
||||
se.items[0].allow_zero_valuation_rate = 1
|
||||
se.save()
|
||||
se.submit()
|
||||
@@ -1301,9 +1215,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
|
||||
from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company)
|
||||
to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company)
|
||||
rejected_warehouse = create_warehouse(
|
||||
"_Test Rejected Internal To Warehouse New", company=company
|
||||
)
|
||||
rejected_warehouse = create_warehouse("_Test Rejected Internal To Warehouse New", company=company)
|
||||
item_doc = make_item(
|
||||
"Test Internal Transfer Item DS",
|
||||
{
|
||||
@@ -1674,9 +1586,10 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
make_stock_entry(
|
||||
purpose="Material Receipt",
|
||||
item_code=item.name,
|
||||
qty=15,
|
||||
qty=20,
|
||||
company=company,
|
||||
to_warehouse=from_warehouse,
|
||||
posting_date=add_days(today(), -3),
|
||||
)
|
||||
|
||||
# Step 3: Create Delivery Note with Internal Customer
|
||||
@@ -1695,17 +1608,18 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
)
|
||||
|
||||
# Step 4: Create Internal Purchase Receipt
|
||||
from erpnext.controllers.status_updater import OverAllowanceError
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
|
||||
|
||||
pr = make_inter_company_purchase_receipt(dn.name)
|
||||
pr.set_posting_time = 1
|
||||
pr.posting_date = today()
|
||||
pr.items[0].qty = 15
|
||||
pr.items[0].from_warehouse = target_warehouse
|
||||
pr.items[0].warehouse = to_warehouse
|
||||
pr.items[0].rejected_warehouse = from_warehouse
|
||||
pr.save()
|
||||
|
||||
self.assertRaises(OverAllowanceError, pr.submit)
|
||||
self.assertRaises(frappe.ValidationError, pr.submit)
|
||||
|
||||
# Step 5: Test Over Receipt Allowance
|
||||
frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 50)
|
||||
@@ -1717,8 +1631,10 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
company=company,
|
||||
from_warehouse=from_warehouse,
|
||||
to_warehouse=target_warehouse,
|
||||
posting_date=add_days(pr.posting_date, -1),
|
||||
)
|
||||
|
||||
pr.reload()
|
||||
pr.submit()
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 0)
|
||||
@@ -1841,7 +1757,6 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
)
|
||||
|
||||
# Step 4: Create Internal Purchase Receipt
|
||||
from erpnext.controllers.status_updater import OverAllowanceError
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
|
||||
|
||||
pr = make_inter_company_purchase_receipt(dn.name)
|
||||
@@ -2147,9 +2062,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
)
|
||||
|
||||
# Step - 4: Create a Material Issue Stock Entry (Qty = 100, Basic Rate = 18.18 [Auto Fetched])
|
||||
make_stock_entry(
|
||||
purpose="Material Issue", item_code=item_code, from_warehouse=warehouse, qty=100
|
||||
)
|
||||
make_stock_entry(purpose="Material Issue", item_code=item_code, from_warehouse=warehouse, qty=100)
|
||||
|
||||
# Step - 5: Create a Return Purchase Return (Qty = -8, Rate = 100 [Auto fetched])
|
||||
return_pr = make_purchase_receipt(
|
||||
@@ -2221,9 +2134,9 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
gl_entries = get_gl_entries(pr_return.doctype, pr_return.name)
|
||||
|
||||
# Test - 1: SLE Stock Value Difference should be equal to Qty * Average Rate
|
||||
average_rate = (
|
||||
(se.items[0].qty * se.items[0].basic_rate) + (pr.items[0].qty * pr.items[0].rate)
|
||||
) / (se.items[0].qty + pr.items[0].qty)
|
||||
average_rate = ((se.items[0].qty * se.items[0].basic_rate) + (pr.items[0].qty * pr.items[0].rate)) / (
|
||||
se.items[0].qty + pr.items[0].qty
|
||||
)
|
||||
expected_stock_value_difference = pr_return.items[0].qty * average_rate
|
||||
self.assertEqual(
|
||||
flt(sl_entries[0].stock_value_difference, 2), flt(expected_stock_value_difference, 2)
|
||||
@@ -2251,6 +2164,143 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
pr_doc.reload()
|
||||
self.assertFalse(pr_doc.items[0].from_warehouse)
|
||||
|
||||
def test_do_not_delete_batch_implicitly(self):
|
||||
item = make_item(
|
||||
"_Test Item With Delete Batch",
|
||||
{"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "TBWDB.#####"},
|
||||
).name
|
||||
|
||||
pr = make_purchase_receipt(item_code=item, qty=10, rate=100)
|
||||
batch_no = pr.items[0].batch_no
|
||||
self.assertTrue(frappe.db.exists("Batch", batch_no))
|
||||
|
||||
pr.cancel()
|
||||
self.assertTrue(frappe.db.exists("Batch", batch_no))
|
||||
|
||||
def test_pr_billed_amount_against_return_entry(self):
|
||||
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_debit_note
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
||||
make_purchase_invoice as make_pi_from_pr,
|
||||
)
|
||||
|
||||
# Create a Purchase Receipt and Fully Bill it
|
||||
pr = make_purchase_receipt(qty=10)
|
||||
pi = make_pi_from_pr(pr.name)
|
||||
pi.insert()
|
||||
pi.submit()
|
||||
|
||||
# Debit Note - 50% Qty & enable updating PR billed amount
|
||||
pi_return = make_debit_note(pi.name)
|
||||
pi_return.items[0].qty = -5
|
||||
pi_return.update_billed_amount_in_purchase_receipt = 1
|
||||
pi_return.submit()
|
||||
|
||||
# Check if the billed amount reduced
|
||||
pr.reload()
|
||||
self.assertEqual(pr.per_billed, 50)
|
||||
|
||||
pi_return.reload()
|
||||
pi_return.cancel()
|
||||
|
||||
# Debit Note - 50% Qty & disable updating PR billed amount
|
||||
pi_return = make_debit_note(pi.name)
|
||||
pi_return.items[0].qty = -5
|
||||
pi_return.update_billed_amount_in_purchase_receipt = 0
|
||||
pi_return.submit()
|
||||
|
||||
# Check if the billed amount stayed the same
|
||||
pr.reload()
|
||||
self.assertEqual(pr.per_billed, 100)
|
||||
|
||||
def test_sle_qty_after_transaction(self):
|
||||
item = make_item(
|
||||
"_Test Item Qty After Transaction",
|
||||
properties={"is_stock_item": 1, "valuation_method": "FIFO"},
|
||||
).name
|
||||
|
||||
posting_date = today()
|
||||
posting_time = nowtime()
|
||||
|
||||
# Step 1: Create Purchase Receipt
|
||||
pr = make_purchase_receipt(
|
||||
item_code=item,
|
||||
qty=1,
|
||||
rate=100,
|
||||
posting_date=posting_date,
|
||||
posting_time=posting_time,
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
for _i in range(9):
|
||||
pr.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": item,
|
||||
"qty": 1,
|
||||
"rate": 100,
|
||||
"warehouse": pr.items[0].warehouse,
|
||||
"cost_center": pr.items[0].cost_center,
|
||||
"expense_account": pr.items[0].expense_account,
|
||||
"uom": pr.items[0].uom,
|
||||
"stock_uom": pr.items[0].stock_uom,
|
||||
"conversion_factor": pr.items[0].conversion_factor,
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(len(pr.items), 10)
|
||||
pr.save()
|
||||
pr.submit()
|
||||
|
||||
data = frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
fields=["qty_after_transaction", "creation", "posting_datetime"],
|
||||
filters={"voucher_no": pr.name, "is_cancelled": 0},
|
||||
order_by="creation",
|
||||
)
|
||||
|
||||
for index, d in enumerate(data):
|
||||
self.assertEqual(d.qty_after_transaction, 1 + index)
|
||||
|
||||
# Step 2: Create Purchase Receipt
|
||||
pr = make_purchase_receipt(
|
||||
item_code=item,
|
||||
qty=1,
|
||||
rate=100,
|
||||
posting_date=posting_date,
|
||||
posting_time=posting_time,
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
for _i in range(9):
|
||||
pr.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": item,
|
||||
"qty": 1,
|
||||
"rate": 100,
|
||||
"warehouse": pr.items[0].warehouse,
|
||||
"cost_center": pr.items[0].cost_center,
|
||||
"expense_account": pr.items[0].expense_account,
|
||||
"uom": pr.items[0].uom,
|
||||
"stock_uom": pr.items[0].stock_uom,
|
||||
"conversion_factor": pr.items[0].conversion_factor,
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(len(pr.items), 10)
|
||||
pr.save()
|
||||
pr.submit()
|
||||
|
||||
data = frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
fields=["qty_after_transaction", "creation", "posting_datetime"],
|
||||
filters={"voucher_no": pr.name, "is_cancelled": 0},
|
||||
order_by="creation",
|
||||
)
|
||||
|
||||
for index, d in enumerate(data):
|
||||
self.assertEqual(d.qty_after_transaction, 11 + index)
|
||||
|
||||
|
||||
def prepare_data_for_internal_transfer():
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||
@@ -2308,7 +2358,6 @@ def get_gl_entries(voucher_type, voucher_no):
|
||||
|
||||
|
||||
def get_taxes(**args):
|
||||
|
||||
args = frappe._dict(args)
|
||||
|
||||
return [
|
||||
@@ -2428,15 +2477,14 @@ def make_purchase_receipt(**args):
|
||||
"rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC"
|
||||
if rejected_qty != 0
|
||||
else "",
|
||||
"rate": args.rate if args.rate != None else 50,
|
||||
"rate": args.rate if args.rate is not None else 50,
|
||||
"conversion_factor": args.conversion_factor or 1.0,
|
||||
"stock_qty": flt(qty) * (flt(args.conversion_factor) or 1.0),
|
||||
"serial_no": args.serial_no,
|
||||
"batch_no": args.batch_no,
|
||||
"stock_uom": args.stock_uom or "_Test UOM",
|
||||
"uom": uom,
|
||||
"cost_center": args.cost_center
|
||||
or frappe.get_cached_value("Company", pr.company, "cost_center"),
|
||||
"cost_center": args.cost_center or frappe.get_cached_value("Company", pr.company, "cost_center"),
|
||||
"asset_location": args.location or "Test Location",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -352,7 +352,6 @@
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"print_width": "100px",
|
||||
"read_only_depends_on": "eval: (!parent.is_return && doc.purchase_order && doc.purchase_order_item)",
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
@@ -1055,7 +1054,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-30 16:12:02.364608",
|
||||
"modified": "2023-12-25 22:32:09.801965",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt Item",
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Putaway Rule', {
|
||||
setup: function(frm) {
|
||||
frm.set_query("warehouse", function() {
|
||||
frappe.ui.form.on("Putaway Rule", {
|
||||
setup: function (frm) {
|
||||
frm.set_query("warehouse", function () {
|
||||
return {
|
||||
"filters": {
|
||||
"company": frm.doc.company,
|
||||
"is_group": 0
|
||||
}
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
is_group: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
uom: function(frm) {
|
||||
uom: function (frm) {
|
||||
if (frm.doc.item_code && frm.doc.uom) {
|
||||
return frm.call({
|
||||
method: "erpnext.stock.get_item_details.get_conversion_factor",
|
||||
args: {
|
||||
item_code: frm.doc.item_code,
|
||||
uom: frm.doc.uom
|
||||
uom: frm.doc.uom,
|
||||
},
|
||||
callback: function(r) {
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
let stock_capacity = flt(frm.doc.capacity) * flt(r.message.conversion_factor);
|
||||
frm.set_value('conversion_factor', r.message.conversion_factor);
|
||||
frm.set_value('stock_capacity', stock_capacity);
|
||||
frm.set_value("conversion_factor", r.message.conversion_factor);
|
||||
frm.set_value("stock_capacity", stock_capacity);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
capacity: function(frm) {
|
||||
capacity: function (frm) {
|
||||
let stock_capacity = flt(frm.doc.capacity) * flt(frm.doc.conversion_factor);
|
||||
frm.set_value('stock_capacity', stock_capacity);
|
||||
}
|
||||
frm.set_value("stock_capacity", stock_capacity);
|
||||
},
|
||||
|
||||
// refresh: function(frm) {
|
||||
|
||||
|
||||
@@ -109,9 +109,7 @@ def apply_putaway_rule(doctype, items, company, sync=None, purpose=None):
|
||||
updated_table = add_row(item, pending_qty, source_warehouse or item.warehouse, updated_table)
|
||||
continue
|
||||
|
||||
at_capacity, rules = get_ordered_putaway_rules(
|
||||
item_code, company, source_warehouse=source_warehouse
|
||||
)
|
||||
at_capacity, rules = get_ordered_putaway_rules(item_code, company, source_warehouse=source_warehouse)
|
||||
|
||||
if not rules:
|
||||
warehouse = source_warehouse or item.get("warehouse")
|
||||
@@ -204,7 +202,7 @@ def _items_changed(old, new, doctype: str) -> bool:
|
||||
new_sorted = sorted(new, key=sort_key)
|
||||
|
||||
# Once sorted by all relevant keys both tables should align if they are same.
|
||||
for old_item, new_item in zip(old_sorted, new_sorted):
|
||||
for old_item, new_item in zip(old_sorted, new_sorted, strict=False):
|
||||
for key in compare_keys:
|
||||
if old_item.get(key) != new_item.get(key):
|
||||
return True
|
||||
@@ -253,9 +251,7 @@ def add_row(item, to_allocate, warehouse, updated_table, rule=None, serial_nos=N
|
||||
|
||||
if item.doctype == "Stock Entry Detail":
|
||||
new_updated_table_row.t_warehouse = warehouse
|
||||
new_updated_table_row.transfer_qty = flt(to_allocate) * flt(
|
||||
new_updated_table_row.conversion_factor
|
||||
)
|
||||
new_updated_table_row.transfer_qty = flt(to_allocate) * flt(new_updated_table_row.conversion_factor)
|
||||
else:
|
||||
new_updated_table_row.stock_qty = flt(to_allocate) * flt(new_updated_table_row.conversion_factor)
|
||||
new_updated_table_row.warehouse = warehouse
|
||||
@@ -277,24 +273,20 @@ def show_unassigned_items_message(items_not_accomodated):
|
||||
|
||||
for entry in items_not_accomodated:
|
||||
item_link = frappe.utils.get_link_to_form("Item", entry[0])
|
||||
formatted_item_rows += """
|
||||
<td>{0}</td>
|
||||
<td>{1}</td>
|
||||
</tr>""".format(
|
||||
item_link, frappe.bold(entry[1])
|
||||
)
|
||||
formatted_item_rows += f"""
|
||||
<td>{item_link}</td>
|
||||
<td>{frappe.bold(entry[1])}</td>
|
||||
</tr>"""
|
||||
|
||||
msg += """
|
||||
<table class="table">
|
||||
<thead>
|
||||
<td>{0}</td>
|
||||
<td>{1}</td>
|
||||
<td>{}</td>
|
||||
<td>{}</td>
|
||||
</thead>
|
||||
{2}
|
||||
{}
|
||||
</table>
|
||||
""".format(
|
||||
_("Item"), _("Unassigned Qty"), formatted_item_rows
|
||||
)
|
||||
""".format(_("Item"), _("Unassigned Qty"), formatted_item_rows)
|
||||
|
||||
frappe.msgprint(msg, title=_("Insufficient Capacity"), is_minimizable=True, wide=True)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
frappe.listview_settings['Putaway Rule'] = {
|
||||
frappe.listview_settings["Putaway Rule"] = {
|
||||
add_fields: ["disable"],
|
||||
get_indicator: (doc) => {
|
||||
if (doc.disable) {
|
||||
@@ -10,9 +10,9 @@ frappe.listview_settings['Putaway Rule'] = {
|
||||
|
||||
reports: [
|
||||
{
|
||||
name: 'Warehouse Capacity Summary',
|
||||
report_type: 'Page',
|
||||
route: 'warehouse-capacity-summary'
|
||||
}
|
||||
]
|
||||
name: "Warehouse Capacity Summary",
|
||||
report_type: "Page",
|
||||
route: "warehouse-capacity-summary",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -46,9 +46,7 @@ class TestPutawayRule(FrappeTestCase):
|
||||
|
||||
def test_putaway_rules_priority(self):
|
||||
"""Test if rule is applied by priority, irrespective of free space."""
|
||||
rule_1 = create_putaway_rule(
|
||||
item_code="_Rice", warehouse=self.warehouse_1, capacity=200, uom="Kg"
|
||||
)
|
||||
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200, uom="Kg")
|
||||
rule_2 = create_putaway_rule(
|
||||
item_code="_Rice", warehouse=self.warehouse_2, capacity=300, uom="Kg", priority=2
|
||||
)
|
||||
@@ -69,17 +67,11 @@ class TestPutawayRule(FrappeTestCase):
|
||||
def test_putaway_rules_with_same_priority(self):
|
||||
"""Test if rule with more free space is applied,
|
||||
among two rules with same priority and capacity."""
|
||||
rule_1 = create_putaway_rule(
|
||||
item_code="_Rice", warehouse=self.warehouse_1, capacity=500, uom="Kg"
|
||||
)
|
||||
rule_2 = create_putaway_rule(
|
||||
item_code="_Rice", warehouse=self.warehouse_2, capacity=500, uom="Kg"
|
||||
)
|
||||
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=500, uom="Kg")
|
||||
rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=500, uom="Kg")
|
||||
|
||||
# out of 500 kg capacity, occupy 100 kg in warehouse_1
|
||||
stock_receipt = make_stock_entry(
|
||||
item_code="_Rice", target=self.warehouse_1, qty=100, basic_rate=50
|
||||
)
|
||||
stock_receipt = make_stock_entry(item_code="_Rice", target=self.warehouse_1, qty=100, basic_rate=50)
|
||||
|
||||
pr = make_purchase_receipt(item_code="_Rice", qty=700, apply_putaway_rule=1, do_not_submit=1)
|
||||
self.assertEqual(len(pr.items), 2)
|
||||
@@ -97,12 +89,8 @@ class TestPutawayRule(FrappeTestCase):
|
||||
|
||||
def test_putaway_rules_with_insufficient_capacity(self):
|
||||
"""Test if qty exceeding capacity, is handled."""
|
||||
rule_1 = create_putaway_rule(
|
||||
item_code="_Rice", warehouse=self.warehouse_1, capacity=100, uom="Kg"
|
||||
)
|
||||
rule_2 = create_putaway_rule(
|
||||
item_code="_Rice", warehouse=self.warehouse_2, capacity=200, uom="Kg"
|
||||
)
|
||||
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=100, uom="Kg")
|
||||
rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=200, uom="Kg")
|
||||
|
||||
pr = make_purchase_receipt(item_code="_Rice", qty=350, apply_putaway_rule=1, do_not_submit=1)
|
||||
self.assertEqual(len(pr.items), 2)
|
||||
@@ -123,19 +111,13 @@ class TestPutawayRule(FrappeTestCase):
|
||||
item.append("uoms", {"uom": "Bag", "conversion_factor": 1000})
|
||||
item.save()
|
||||
|
||||
rule_1 = create_putaway_rule(
|
||||
item_code="_Rice", warehouse=self.warehouse_1, capacity=3, uom="Bag"
|
||||
)
|
||||
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=3, uom="Bag")
|
||||
self.assertEqual(rule_1.stock_capacity, 3000)
|
||||
rule_2 = create_putaway_rule(
|
||||
item_code="_Rice", warehouse=self.warehouse_2, capacity=4, uom="Bag"
|
||||
)
|
||||
rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=4, uom="Bag")
|
||||
self.assertEqual(rule_2.stock_capacity, 4000)
|
||||
|
||||
# populate 'Rack 1' with 1 Bag, making the free space 2 Bags
|
||||
stock_receipt = make_stock_entry(
|
||||
item_code="_Rice", target=self.warehouse_1, qty=1000, basic_rate=50
|
||||
)
|
||||
stock_receipt = make_stock_entry(item_code="_Rice", target=self.warehouse_1, qty=1000, basic_rate=50)
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
item_code="_Rice",
|
||||
@@ -167,9 +149,7 @@ class TestPutawayRule(FrappeTestCase):
|
||||
frappe.db.set_value("UOM", "Bag", "must_be_whole_number", 1)
|
||||
|
||||
# Putaway Rule in different UOM
|
||||
rule_1 = create_putaway_rule(
|
||||
item_code="_Rice", warehouse=self.warehouse_1, capacity=1, uom="Bag"
|
||||
)
|
||||
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=1, uom="Bag")
|
||||
self.assertEqual(rule_1.stock_capacity, 1000)
|
||||
# Putaway Rule in Stock UOM
|
||||
rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=500)
|
||||
@@ -199,9 +179,7 @@ class TestPutawayRule(FrappeTestCase):
|
||||
|
||||
def test_putaway_rules_with_reoccurring_item(self):
|
||||
"""Test rules on same item entered multiple times with different rate."""
|
||||
rule_1 = create_putaway_rule(
|
||||
item_code="_Rice", warehouse=self.warehouse_1, capacity=200, uom="Kg"
|
||||
)
|
||||
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200, uom="Kg")
|
||||
# total capacity is 200 Kg
|
||||
|
||||
pr = make_purchase_receipt(item_code="_Rice", qty=100, apply_putaway_rule=1, do_not_submit=1)
|
||||
@@ -237,9 +215,7 @@ class TestPutawayRule(FrappeTestCase):
|
||||
|
||||
def test_validate_over_receipt_in_warehouse(self):
|
||||
"""Test if overreceipt is blocked in the presence of putaway rules."""
|
||||
rule_1 = create_putaway_rule(
|
||||
item_code="_Rice", warehouse=self.warehouse_1, capacity=200, uom="Kg"
|
||||
)
|
||||
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200, uom="Kg")
|
||||
|
||||
pr = make_purchase_receipt(item_code="_Rice", qty=300, apply_putaway_rule=1, do_not_submit=1)
|
||||
self.assertEqual(len(pr.items), 1)
|
||||
@@ -291,9 +267,7 @@ class TestPutawayRule(FrappeTestCase):
|
||||
|
||||
def test_putaway_rule_on_stock_entry_material_transfer_reoccuring_item(self):
|
||||
"""Test if reoccuring item is correctly considered."""
|
||||
rule_1 = create_putaway_rule(
|
||||
item_code="_Rice", warehouse=self.warehouse_1, capacity=300, uom="Kg"
|
||||
)
|
||||
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=300, uom="Kg")
|
||||
rule_2 = create_putaway_rule(
|
||||
item_code="_Rice", warehouse=self.warehouse_2, capacity=600, uom="Kg", priority=2
|
||||
)
|
||||
@@ -428,9 +402,7 @@ class TestPutawayRule(FrappeTestCase):
|
||||
rule_1 = create_putaway_rule(
|
||||
item_code="_Rice", warehouse=self.warehouse_1, capacity=200, uom="Kg"
|
||||
) # more capacity
|
||||
rule_2 = create_putaway_rule(
|
||||
item_code="_Rice", warehouse=self.warehouse_2, capacity=100, uom="Kg"
|
||||
)
|
||||
rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=100, uom="Kg")
|
||||
|
||||
stock_entry = make_stock_entry(
|
||||
item_code="_Rice",
|
||||
@@ -480,9 +452,7 @@ def create_putaway_rule(**args):
|
||||
putaway.capacity = args.capacity or 1
|
||||
putaway.stock_uom = frappe.db.get_value("Item", putaway.item_code, "stock_uom")
|
||||
putaway.uom = args.uom or putaway.stock_uom
|
||||
putaway.conversion_factor = get_conversion_factor(putaway.item_code, putaway.uom)[
|
||||
"conversion_factor"
|
||||
]
|
||||
putaway.conversion_factor = get_conversion_factor(putaway.item_code, putaway.uom)["conversion_factor"]
|
||||
|
||||
if not args.do_not_save:
|
||||
putaway.save()
|
||||
|
||||
@@ -4,88 +4,85 @@
|
||||
cur_frm.cscript.refresh = cur_frm.cscript.inspection_type;
|
||||
|
||||
frappe.ui.form.on("Quality Inspection", {
|
||||
|
||||
setup: function(frm) {
|
||||
frm.set_query("reference_name", function() {
|
||||
setup: function (frm) {
|
||||
frm.set_query("reference_name", function () {
|
||||
return {
|
||||
filters: {
|
||||
"docstatus": ["!=", 2],
|
||||
}
|
||||
}
|
||||
docstatus: ["!=", 2],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("batch_no", function() {
|
||||
frm.set_query("batch_no", function () {
|
||||
return {
|
||||
filters: {
|
||||
"item": frm.doc.item_code
|
||||
}
|
||||
item: frm.doc.item_code,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Serial No based on item_code
|
||||
frm.set_query("item_serial_no", function() {
|
||||
frm.set_query("item_serial_no", function () {
|
||||
let filters = {};
|
||||
if (frm.doc.item_code) {
|
||||
filters = {
|
||||
'item_code': frm.doc.item_code
|
||||
item_code: frm.doc.item_code,
|
||||
};
|
||||
}
|
||||
return { filters: filters };
|
||||
});
|
||||
|
||||
// item code based on GRN/DN
|
||||
frm.set_query("item_code", function(doc) {
|
||||
frm.set_query("item_code", function (doc) {
|
||||
let doctype = doc.reference_type;
|
||||
|
||||
if (doc.reference_type !== "Job Card") {
|
||||
doctype = (doc.reference_type == "Stock Entry") ?
|
||||
"Stock Entry Detail" : doc.reference_type + " Item";
|
||||
doctype =
|
||||
doc.reference_type == "Stock Entry" ? "Stock Entry Detail" : doc.reference_type + " Item";
|
||||
}
|
||||
|
||||
if (doc.reference_type && doc.reference_name) {
|
||||
let filters = {
|
||||
"from": doctype,
|
||||
"inspection_type": doc.inspection_type
|
||||
from: doctype,
|
||||
inspection_type: doc.inspection_type,
|
||||
};
|
||||
|
||||
if (doc.reference_type == doctype)
|
||||
filters["reference_name"] = doc.reference_name;
|
||||
else
|
||||
filters["parent"] = doc.reference_name;
|
||||
if (doc.reference_type == doctype) filters["reference_name"] = doc.reference_name;
|
||||
else filters["parent"] = doc.reference_name;
|
||||
|
||||
return {
|
||||
query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query",
|
||||
filters: filters
|
||||
filters: filters,
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
refresh: function (frm) {
|
||||
// Ignore cancellation of reference doctype on cancel all.
|
||||
frm.ignore_doctypes_on_cancel_all = [frm.doc.reference_type];
|
||||
},
|
||||
|
||||
item_code: function(frm) {
|
||||
item_code: function (frm) {
|
||||
if (frm.doc.item_code && !frm.doc.quality_inspection_template) {
|
||||
return frm.call({
|
||||
method: "get_quality_inspection_template",
|
||||
doc: frm.doc,
|
||||
callback: function() {
|
||||
refresh_field(['quality_inspection_template', 'readings']);
|
||||
}
|
||||
callback: function () {
|
||||
refresh_field(["quality_inspection_template", "readings"]);
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
quality_inspection_template: function(frm) {
|
||||
quality_inspection_template: function (frm) {
|
||||
if (frm.doc.quality_inspection_template) {
|
||||
return frm.call({
|
||||
method: "get_item_specification_details",
|
||||
doc: frm.doc,
|
||||
callback: function() {
|
||||
refresh_field('readings');
|
||||
}
|
||||
callback: function () {
|
||||
refresh_field("readings");
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -68,6 +68,9 @@ class QualityInspection(Document):
|
||||
def on_cancel(self):
|
||||
self.update_qc_reference()
|
||||
|
||||
def on_trash(self):
|
||||
self.update_qc_reference()
|
||||
|
||||
def validate_readings_status_mandatory(self):
|
||||
for reading in self.readings:
|
||||
if not reading.status:
|
||||
@@ -79,13 +82,11 @@ class QualityInspection(Document):
|
||||
if self.reference_type == "Job Card":
|
||||
if self.reference_name:
|
||||
frappe.db.sql(
|
||||
"""
|
||||
UPDATE `tab{doctype}`
|
||||
f"""
|
||||
UPDATE `tab{self.reference_type}`
|
||||
SET quality_inspection = %s, modified = %s
|
||||
WHERE name = %s and production_item = %s
|
||||
""".format(
|
||||
doctype=self.reference_type
|
||||
),
|
||||
""",
|
||||
(quality_inspection, self.modified, self.reference_name, self.item_code),
|
||||
)
|
||||
|
||||
@@ -107,9 +108,9 @@ class QualityInspection(Document):
|
||||
args.append(self.name)
|
||||
|
||||
frappe.db.sql(
|
||||
"""
|
||||
f"""
|
||||
UPDATE
|
||||
`tab{child_doc}` t1, `tab{parent_doc}` t2
|
||||
`tab{doctype}` t1, `tab{self.reference_type}` t2
|
||||
SET
|
||||
t1.quality_inspection = %s, t2.modified = %s
|
||||
WHERE
|
||||
@@ -117,9 +118,7 @@ class QualityInspection(Document):
|
||||
and t1.item_code = %s
|
||||
and t1.parent = t2.name
|
||||
{conditions}
|
||||
""".format(
|
||||
parent_doc=self.reference_type, child_doc=doctype, conditions=conditions
|
||||
),
|
||||
""",
|
||||
args,
|
||||
)
|
||||
|
||||
@@ -177,9 +176,9 @@ class QualityInspection(Document):
|
||||
except NameError as e:
|
||||
field = frappe.bold(e.args[0].split()[1])
|
||||
frappe.throw(
|
||||
_("Row #{0}: {1} is not a valid reading field. Please refer to the field description.").format(
|
||||
reading.idx, field
|
||||
),
|
||||
_(
|
||||
"Row #{0}: {1} is not a valid reading field. Please refer to the field description."
|
||||
).format(reading.idx, field),
|
||||
title=_("Invalid Formula"),
|
||||
)
|
||||
except Exception:
|
||||
@@ -248,40 +247,26 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
qi_condition = ""
|
||||
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
f"""
|
||||
SELECT item_code
|
||||
FROM `tab{doc}`
|
||||
FROM `tab{from_doctype}`
|
||||
WHERE parent=%(parent)s and docstatus < 2 and item_code like %(txt)s
|
||||
{qi_condition} {cond} {mcond}
|
||||
ORDER BY item_code limit {page_len} offset {start}
|
||||
""".format(
|
||||
doc=from_doctype,
|
||||
cond=cond,
|
||||
mcond=mcond,
|
||||
start=cint(start),
|
||||
page_len=cint(page_len),
|
||||
qi_condition=qi_condition,
|
||||
),
|
||||
ORDER BY item_code limit {cint(page_len)} offset {cint(start)}
|
||||
""",
|
||||
{"parent": filters.get("parent"), "txt": "%%%s%%" % txt},
|
||||
)
|
||||
|
||||
elif filters.get("reference_name"):
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
f"""
|
||||
SELECT production_item
|
||||
FROM `tab{doc}`
|
||||
FROM `tab{from_doctype}`
|
||||
WHERE name = %(reference_name)s and docstatus < 2 and production_item like %(txt)s
|
||||
{qi_condition} {cond} {mcond}
|
||||
ORDER BY production_item
|
||||
limit {page_len} offset {start}
|
||||
""".format(
|
||||
doc=from_doctype,
|
||||
cond=cond,
|
||||
mcond=mcond,
|
||||
start=cint(start),
|
||||
page_len=cint(page_len),
|
||||
qi_condition=qi_condition,
|
||||
),
|
||||
limit {cint(page_len)} offset {cint(start)}
|
||||
""",
|
||||
{"reference_name": filters.get("reference_name"), "txt": "%%%s%%" % txt},
|
||||
)
|
||||
|
||||
|
||||
@@ -159,9 +159,7 @@ class TestQualityInspection(FrappeTestCase):
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
readings = [
|
||||
{"specification": "Iron Content", "min_value": 0.1, "max_value": 0.9, "reading_1": "1.0"}
|
||||
]
|
||||
readings = [{"specification": "Iron Content", "min_value": 0.1, "max_value": 0.9, "reading_1": "1.0"}]
|
||||
|
||||
qa = create_quality_inspection(
|
||||
reference_type="Stock Entry", reference_name=se.name, readings=readings, status="Rejected"
|
||||
@@ -216,6 +214,33 @@ class TestQualityInspection(FrappeTestCase):
|
||||
qa.save()
|
||||
self.assertEqual(qa.status, "Accepted")
|
||||
|
||||
def test_delete_quality_inspection_linked_with_stock_entry(self):
|
||||
item_code = create_item("_Test Cicuular Dependecy Item with QA").name
|
||||
|
||||
se = make_stock_entry(
|
||||
item_code=item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100, do_not_submit=True
|
||||
)
|
||||
|
||||
se.inspection_required = 1
|
||||
se.save()
|
||||
|
||||
qa = create_quality_inspection(
|
||||
item_code=item_code, reference_type="Stock Entry", reference_name=se.name, do_not_submit=True
|
||||
)
|
||||
|
||||
se.reload()
|
||||
se.items[0].quality_inspection = qa.name
|
||||
se.save()
|
||||
|
||||
qa.delete()
|
||||
|
||||
se.reload()
|
||||
|
||||
qc = se.items[0].quality_inspection
|
||||
self.assertFalse(qc)
|
||||
|
||||
se.delete()
|
||||
|
||||
|
||||
def create_quality_inspection(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Quality Inspection Parameter', {
|
||||
frappe.ui.form.on("Quality Inspection Parameter", {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Quality Inspection Parameter Group', {
|
||||
frappe.ui.form.on("Quality Inspection Parameter Group", {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Quality Inspection Template', {
|
||||
refresh: function() {
|
||||
|
||||
}
|
||||
frappe.ui.form.on("Quality Inspection Template", {
|
||||
refresh: function () {},
|
||||
});
|
||||
|
||||
@@ -1,91 +1,90 @@
|
||||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Quick Stock Balance', {
|
||||
|
||||
frappe.ui.form.on("Quick Stock Balance", {
|
||||
setup: (frm) => {
|
||||
frm.set_query('item', () => {
|
||||
frm.set_query("item", () => {
|
||||
if (!(frm.doc.warehouse && frm.doc.date)) {
|
||||
frm.trigger('check_warehouse_and_date');
|
||||
frm.trigger("check_warehouse_and_date");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
make_custom_stock_report_button: (frm) => {
|
||||
if (frm.doc.item) {
|
||||
frm.add_custom_button(__('Stock Balance Report'), () => {
|
||||
frappe.set_route('query-report', 'Stock Balance',
|
||||
{ 'item_code': frm.doc.item, 'warehouse': frm.doc.warehouse });
|
||||
frm.add_custom_button(__("Stock Balance Report"), () => {
|
||||
frappe.set_route("query-report", "Stock Balance", {
|
||||
item_code: frm.doc.item,
|
||||
warehouse: frm.doc.warehouse,
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
refresh: (frm) => {
|
||||
frm.disable_save();
|
||||
frm.trigger('make_custom_stock_report_button');
|
||||
frm.trigger("make_custom_stock_report_button");
|
||||
},
|
||||
|
||||
check_warehouse_and_date: (frm) => {
|
||||
frappe.msgprint(__('Please enter Warehouse and Date'));
|
||||
frm.doc.item = '';
|
||||
frappe.msgprint(__("Please enter Warehouse and Date"));
|
||||
frm.doc.item = "";
|
||||
frm.refresh();
|
||||
},
|
||||
|
||||
warehouse: (frm) => {
|
||||
if (frm.doc.item || frm.doc.item_barcode) {
|
||||
frm.trigger('get_stock_and_item_details');
|
||||
frm.trigger("get_stock_and_item_details");
|
||||
}
|
||||
},
|
||||
|
||||
date: (frm) => {
|
||||
if (frm.doc.item || frm.doc.item_barcode) {
|
||||
frm.trigger('get_stock_and_item_details');
|
||||
frm.trigger("get_stock_and_item_details");
|
||||
}
|
||||
},
|
||||
|
||||
item: (frm) => {
|
||||
frappe.flags.last_updated_element = 'item';
|
||||
frm.trigger('get_stock_and_item_details');
|
||||
frm.trigger('make_custom_stock_report_button');
|
||||
frappe.flags.last_updated_element = "item";
|
||||
frm.trigger("get_stock_and_item_details");
|
||||
frm.trigger("make_custom_stock_report_button");
|
||||
},
|
||||
|
||||
item_barcode: (frm) => {
|
||||
frappe.flags.last_updated_element = 'item_barcode';
|
||||
frm.trigger('get_stock_and_item_details');
|
||||
frm.trigger('make_custom_stock_report_button');
|
||||
frappe.flags.last_updated_element = "item_barcode";
|
||||
frm.trigger("get_stock_and_item_details");
|
||||
frm.trigger("make_custom_stock_report_button");
|
||||
},
|
||||
|
||||
get_stock_and_item_details: (frm) => {
|
||||
if (!(frm.doc.warehouse && frm.doc.date)) {
|
||||
frm.trigger('check_warehouse_and_date');
|
||||
}
|
||||
else if (frm.doc.item || frm.doc.item_barcode) {
|
||||
frm.trigger("check_warehouse_and_date");
|
||||
} else if (frm.doc.item || frm.doc.item_barcode) {
|
||||
let filters = {
|
||||
warehouse: frm.doc.warehouse,
|
||||
date: frm.doc.date,
|
||||
};
|
||||
if (frappe.flags.last_updated_element === 'item') {
|
||||
filters = { ...filters, ...{ item: frm.doc.item }};
|
||||
}
|
||||
else {
|
||||
filters = { ...filters, ...{ barcode: frm.doc.item_barcode }};
|
||||
if (frappe.flags.last_updated_element === "item") {
|
||||
filters = { ...filters, ...{ item: frm.doc.item } };
|
||||
} else {
|
||||
filters = { ...filters, ...{ barcode: frm.doc.item_barcode } };
|
||||
}
|
||||
frappe.call({
|
||||
method: 'erpnext.stock.doctype.quick_stock_balance.quick_stock_balance.get_stock_item_details',
|
||||
method: "erpnext.stock.doctype.quick_stock_balance.quick_stock_balance.get_stock_item_details",
|
||||
args: filters,
|
||||
callback: (r) => {
|
||||
if (r.message) {
|
||||
let fields = ['item', 'qty', 'value', 'image'];
|
||||
if (!r.message['barcodes'].includes(frm.doc.item_barcode)) {
|
||||
frm.doc.item_barcode = '';
|
||||
let fields = ["item", "qty", "value", "image"];
|
||||
if (!r.message["barcodes"].includes(frm.doc.item_barcode)) {
|
||||
frm.doc.item_barcode = "";
|
||||
frm.refresh();
|
||||
}
|
||||
fields.forEach(function (field) {
|
||||
frm.set_value(field, r.message[field]);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,17 +17,13 @@ class QuickStockBalance(Document):
|
||||
def get_stock_item_details(warehouse, date, item=None, barcode=None):
|
||||
out = {}
|
||||
if barcode:
|
||||
out["item"] = frappe.db.get_value(
|
||||
"Item Barcode", filters={"barcode": barcode}, fieldname=["parent"]
|
||||
)
|
||||
out["item"] = frappe.db.get_value("Item Barcode", filters={"barcode": barcode}, fieldname=["parent"])
|
||||
if not out["item"]:
|
||||
frappe.throw(_("Invalid Barcode. There is no Item attached to this barcode."))
|
||||
else:
|
||||
out["item"] = item
|
||||
|
||||
barcodes = frappe.db.get_values(
|
||||
"Item Barcode", filters={"parent": out["item"]}, fieldname=["barcode"]
|
||||
)
|
||||
barcodes = frappe.db.get_values("Item Barcode", filters={"parent": out["item"]}, fieldname=["barcode"])
|
||||
|
||||
out["barcodes"] = [x[0] for x in barcodes]
|
||||
out["qty"] = get_stock_balance(out["item"], warehouse, date)
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Repost Item Valuation', {
|
||||
setup: function(frm) {
|
||||
frappe.ui.form.on("Repost Item Valuation", {
|
||||
setup: function (frm) {
|
||||
frm.set_query("warehouse", () => {
|
||||
let filters = {
|
||||
'is_group': 0
|
||||
is_group: 0,
|
||||
};
|
||||
if (frm.doc.company) filters['company'] = frm.doc.company;
|
||||
return {filters: filters};
|
||||
if (frm.doc.company) filters["company"] = frm.doc.company;
|
||||
return { filters: filters };
|
||||
});
|
||||
|
||||
frm.set_query("voucher_type", () => {
|
||||
return {
|
||||
filters: {
|
||||
name: ['in', ['Purchase Receipt', 'Purchase Invoice', 'Delivery Note',
|
||||
'Sales Invoice', 'Stock Entry', 'Stock Reconciliation', 'Subcontracting Receipt']]
|
||||
}
|
||||
name: [
|
||||
"in",
|
||||
[
|
||||
"Purchase Receipt",
|
||||
"Purchase Invoice",
|
||||
"Delivery Note",
|
||||
"Sales Invoice",
|
||||
"Stock Entry",
|
||||
"Stock Reconciliation",
|
||||
"Subcontracting Receipt",
|
||||
],
|
||||
],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -25,97 +35,97 @@ frappe.ui.form.on('Repost Item Valuation', {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
docstatus: 1
|
||||
}
|
||||
docstatus: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
frm.trigger('setup_realtime_progress');
|
||||
frm.trigger("setup_realtime_progress");
|
||||
},
|
||||
|
||||
based_on: function(frm) {
|
||||
based_on: function (frm) {
|
||||
var fields_to_reset = [];
|
||||
|
||||
if (frm.doc.based_on == 'Transaction') {
|
||||
fields_to_reset = ['item_code', 'warehouse'];
|
||||
} else if (frm.doc.based_on == 'Item and Warehouse') {
|
||||
fields_to_reset = ['voucher_type', 'voucher_no'];
|
||||
if (frm.doc.based_on == "Transaction") {
|
||||
fields_to_reset = ["item_code", "warehouse"];
|
||||
} else if (frm.doc.based_on == "Item and Warehouse") {
|
||||
fields_to_reset = ["voucher_type", "voucher_no"];
|
||||
}
|
||||
|
||||
if (fields_to_reset) {
|
||||
fields_to_reset.forEach(field => {
|
||||
fields_to_reset.forEach((field) => {
|
||||
frm.set_value(field, undefined);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
setup_realtime_progress: function(frm) {
|
||||
frappe.realtime.on('item_reposting_progress', data => {
|
||||
setup_realtime_progress: function (frm) {
|
||||
frappe.realtime.on("item_reposting_progress", (data) => {
|
||||
if (frm.doc.name !== data.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (frm.doc.status == 'In Progress') {
|
||||
if (frm.doc.status == "In Progress") {
|
||||
frm.doc.current_index = data.current_index;
|
||||
frm.doc.items_to_be_repost = data.items_to_be_repost;
|
||||
|
||||
frm.dashboard.reset();
|
||||
frm.trigger('show_reposting_progress');
|
||||
frm.trigger("show_reposting_progress");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.status == "Failed" && frm.doc.docstatus==1) {
|
||||
frm.add_custom_button(__('Restart'), function () {
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.status == "Failed" && frm.doc.docstatus == 1) {
|
||||
frm.add_custom_button(__("Restart"), function () {
|
||||
frm.trigger("restart_reposting");
|
||||
}).addClass("btn-primary");
|
||||
}
|
||||
|
||||
frm.trigger('show_reposting_progress');
|
||||
frm.trigger("show_reposting_progress");
|
||||
|
||||
if (frm.doc.status === 'Queued' && frm.doc.docstatus === 1) {
|
||||
frm.trigger('execute_reposting');
|
||||
if (frm.doc.status === "Queued" && frm.doc.docstatus === 1) {
|
||||
frm.trigger("execute_reposting");
|
||||
}
|
||||
},
|
||||
|
||||
execute_reposting(frm) {
|
||||
frm.add_custom_button(__("Start Reposting"), () => {
|
||||
frappe.call({
|
||||
method: 'erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.execute_repost_item_valuation',
|
||||
callback: function() {
|
||||
frappe.msgprint(__('Reposting has been started in the background.'));
|
||||
}
|
||||
method: "erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.execute_repost_item_valuation",
|
||||
callback: function () {
|
||||
frappe.msgprint(__("Reposting has been started in the background."));
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
show_reposting_progress: function(frm) {
|
||||
show_reposting_progress: function (frm) {
|
||||
var bars = [];
|
||||
|
||||
let total_count = frm.doc.items_to_be_repost ? JSON.parse(frm.doc.items_to_be_repost).length : 0;
|
||||
let progress = flt(cint(frm.doc.current_index) / total_count * 100, 2) || 0.5;
|
||||
var title = __('Reposting Completed {0}%', [progress]);
|
||||
let progress = flt((cint(frm.doc.current_index) / total_count) * 100, 2) || 0.5;
|
||||
var title = __("Reposting Completed {0}%", [progress]);
|
||||
|
||||
bars.push({
|
||||
'title': title,
|
||||
'width': progress + '%',
|
||||
'progress_class': 'progress-bar-success'
|
||||
title: title,
|
||||
width: progress + "%",
|
||||
progress_class: "progress-bar-success",
|
||||
});
|
||||
|
||||
frm.dashboard.add_progress(__('Reposting Progress'), bars);
|
||||
frm.dashboard.add_progress(__("Reposting Progress"), bars);
|
||||
},
|
||||
|
||||
restart_reposting: function(frm) {
|
||||
restart_reposting: function (frm) {
|
||||
frappe.call({
|
||||
method: "restart_reposting",
|
||||
doc: frm.doc,
|
||||
callback: function(r) {
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
frm.refresh();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -117,15 +117,12 @@ class RepostItemValuation(Document):
|
||||
if not acc_settings.acc_frozen_upto:
|
||||
return
|
||||
if getdate(self.posting_date) <= getdate(acc_settings.acc_frozen_upto):
|
||||
if (
|
||||
if acc_settings.frozen_accounts_modifier and frappe.session.user in get_users_with_role(
|
||||
acc_settings.frozen_accounts_modifier
|
||||
and frappe.session.user in get_users_with_role(acc_settings.frozen_accounts_modifier)
|
||||
):
|
||||
frappe.msgprint(_("Caution: This might alter frozen accounts."))
|
||||
return
|
||||
frappe.throw(
|
||||
_("You cannot repost item valuation before {}").format(acc_settings.acc_frozen_upto)
|
||||
)
|
||||
frappe.throw(_("You cannot repost item valuation before {}").format(acc_settings.acc_frozen_upto))
|
||||
|
||||
def reset_field_values(self):
|
||||
if self.based_on == "Transaction":
|
||||
@@ -173,14 +170,9 @@ class RepostItemValuation(Document):
|
||||
if self.status not in ("Queued", "In Progress"):
|
||||
return
|
||||
|
||||
if not (self.voucher_no and self.voucher_no):
|
||||
return
|
||||
|
||||
transaction_status = frappe.db.get_value(self.voucher_type, self.voucher_no, "docstatus")
|
||||
if transaction_status == 2:
|
||||
msg = _("Cannot cancel as processing of cancelled documents is pending.")
|
||||
msg += "<br>" + _("Please try again in an hour.")
|
||||
frappe.throw(msg, title=_("Pending processing"))
|
||||
msg = _("Cannot cancel as processing of cancelled documents is pending.")
|
||||
msg += "<br>" + _("Please try again in an hour.")
|
||||
frappe.throw(msg, title=_("Pending processing"))
|
||||
|
||||
@frappe.whitelist()
|
||||
def restart_reposting(self):
|
||||
@@ -226,6 +218,7 @@ def on_doctype_update():
|
||||
|
||||
def repost(doc):
|
||||
try:
|
||||
frappe.flags.through_repost_item_valuation = True
|
||||
if not frappe.db.exists("Repost Item Valuation", doc.name):
|
||||
return
|
||||
|
||||
@@ -248,7 +241,7 @@ def repost(doc):
|
||||
raise
|
||||
|
||||
frappe.db.rollback()
|
||||
traceback = frappe.get_traceback()
|
||||
traceback = frappe.get_traceback(with_context=True)
|
||||
doc.log_error("Unable to repost item valuation")
|
||||
|
||||
message = frappe.message_log.pop() if frappe.message_log else ""
|
||||
|
||||
@@ -179,7 +179,6 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
|
||||
riv3.set_status("Skipped")
|
||||
|
||||
def test_stock_freeze_validation(self):
|
||||
|
||||
today = nowdate()
|
||||
|
||||
riv = frappe.get_doc(
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
cur_frm.add_fetch("customer", "customer_name", "customer_name")
|
||||
cur_frm.add_fetch("supplier", "supplier_name", "supplier_name")
|
||||
cur_frm.add_fetch("customer", "customer_name", "customer_name");
|
||||
cur_frm.add_fetch("supplier", "supplier_name", "supplier_name");
|
||||
|
||||
cur_frm.add_fetch("item_code", "item_name", "item_name")
|
||||
cur_frm.add_fetch("item_code", "description", "description")
|
||||
cur_frm.add_fetch("item_code", "item_group", "item_group")
|
||||
cur_frm.add_fetch("item_code", "brand", "brand")
|
||||
cur_frm.add_fetch("item_code", "item_name", "item_name");
|
||||
cur_frm.add_fetch("item_code", "description", "description");
|
||||
cur_frm.add_fetch("item_code", "item_group", "item_group");
|
||||
cur_frm.add_fetch("item_code", "brand", "brand");
|
||||
|
||||
cur_frm.cscript.onload = function() {
|
||||
cur_frm.set_query("item_code", function() {
|
||||
return erpnext.queries.item({"is_stock_item": 1, "has_serial_no": 1})
|
||||
cur_frm.cscript.onload = function () {
|
||||
cur_frm.set_query("item_code", function () {
|
||||
return erpnext.queries.item({ is_stock_item: 1, has_serial_no: 1 });
|
||||
});
|
||||
};
|
||||
|
||||
frappe.ui.form.on("Serial No", "refresh", function(frm) {
|
||||
frappe.ui.form.on("Serial No", "refresh", function (frm) {
|
||||
frm.toggle_enable("item_code", frm.doc.__islocal);
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
|
||||
import json
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import frappe
|
||||
from frappe import ValidationError, _
|
||||
@@ -66,7 +65,7 @@ class SerialNoDuplicateError(ValidationError):
|
||||
|
||||
class SerialNo(StockController):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SerialNo, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.via_stock_ledger = False
|
||||
|
||||
def validate(self):
|
||||
@@ -123,9 +122,7 @@ class SerialNo(StockController):
|
||||
"""
|
||||
item = frappe.get_cached_doc("Item", self.item_code)
|
||||
if item.has_serial_no != 1:
|
||||
frappe.throw(
|
||||
_("Item {0} is not setup for Serial Nos. Check Item master").format(self.item_code)
|
||||
)
|
||||
frappe.throw(_("Item {0} is not setup for Serial Nos. Check Item master").format(self.item_code))
|
||||
|
||||
self.item_group = item.item_group
|
||||
self.description = item.description
|
||||
@@ -301,7 +298,8 @@ def validate_serial_no(sle, item_det):
|
||||
|
||||
if len(serial_nos) != len(set(serial_nos)):
|
||||
frappe.throw(
|
||||
_("Duplicate Serial No entered for Item {0}").format(sle.item_code), SerialNoDuplicateError
|
||||
_("Duplicate Serial No entered for Item {0}").format(sle.item_code),
|
||||
SerialNoDuplicateError,
|
||||
)
|
||||
|
||||
allow_existing_serial_no = cint(
|
||||
@@ -337,7 +335,9 @@ def validate_serial_no(sle, item_det):
|
||||
if sr.item_code != sle.item_code:
|
||||
if not allow_serial_nos_with_different_item(serial_no, sle):
|
||||
frappe.throw(
|
||||
_("Serial No {0} does not belong to Item {1}").format(serial_no, sle.item_code),
|
||||
_("Serial No {0} does not belong to Item {1}").format(
|
||||
serial_no, sle.item_code
|
||||
),
|
||||
SerialNoItemError,
|
||||
)
|
||||
|
||||
@@ -362,7 +362,9 @@ def validate_serial_no(sle, item_det):
|
||||
frappe.throw(_(msg), SerialNoDuplicateError)
|
||||
|
||||
if cint(sle.actual_qty) > 0 and has_serial_no_exists(sr, sle):
|
||||
doc_name = frappe.bold(get_link_to_form(sr.purchase_document_type, sr.purchase_document_no))
|
||||
doc_name = frappe.bold(
|
||||
get_link_to_form(sr.purchase_document_type, sr.purchase_document_no)
|
||||
)
|
||||
frappe.throw(
|
||||
_("Serial No {0} has already been received in the {1} #{2}").format(
|
||||
frappe.bold(serial_no), sr.purchase_document_type, doc_name
|
||||
@@ -375,25 +377,32 @@ def validate_serial_no(sle, item_det):
|
||||
and sle.voucher_type not in ["Stock Entry", "Stock Reconciliation"]
|
||||
and sle.voucher_type == sr.delivery_document_type
|
||||
):
|
||||
return_against = frappe.db.get_value(sle.voucher_type, sle.voucher_no, "return_against")
|
||||
return_against = frappe.db.get_value(
|
||||
sle.voucher_type, sle.voucher_no, "return_against"
|
||||
)
|
||||
if return_against and return_against != sr.delivery_document_no:
|
||||
frappe.throw(_("Serial no {0} has been already returned").format(sr.name))
|
||||
|
||||
if cint(sle.actual_qty) < 0:
|
||||
if sr.warehouse != sle.warehouse:
|
||||
frappe.throw(
|
||||
_("Serial No {0} does not belong to Warehouse {1}").format(serial_no, sle.warehouse),
|
||||
_("Serial No {0} does not belong to Warehouse {1}").format(
|
||||
serial_no, sle.warehouse
|
||||
),
|
||||
SerialNoWarehouseError,
|
||||
)
|
||||
|
||||
if not sr.purchase_document_no:
|
||||
frappe.throw(_("Serial No {0} not in stock").format(serial_no), SerialNoNotExistsError)
|
||||
frappe.throw(
|
||||
_("Serial No {0} not in stock").format(serial_no), SerialNoNotExistsError
|
||||
)
|
||||
|
||||
if sle.voucher_type in ("Delivery Note", "Sales Invoice"):
|
||||
|
||||
if sr.batch_no and sr.batch_no != sle.batch_no:
|
||||
frappe.throw(
|
||||
_("Serial No {0} does not belong to Batch {1}").format(serial_no, sle.batch_no),
|
||||
_("Serial No {0} does not belong to Batch {1}").format(
|
||||
serial_no, sle.batch_no
|
||||
),
|
||||
SerialNoBatchError,
|
||||
)
|
||||
|
||||
@@ -408,7 +417,11 @@ def validate_serial_no(sle, item_det):
|
||||
if sle.voucher_type == "Sales Invoice":
|
||||
if not frappe.db.exists(
|
||||
"Sales Invoice Item",
|
||||
{"parent": sle.voucher_no, "item_code": sle.item_code, "sales_order": sr.sales_order},
|
||||
{
|
||||
"parent": sle.voucher_no,
|
||||
"item_code": sle.item_code,
|
||||
"sales_order": sr.sales_order,
|
||||
},
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
@@ -431,7 +444,11 @@ def validate_serial_no(sle, item_det):
|
||||
)
|
||||
if not invoice or frappe.db.exists(
|
||||
"Sales Invoice Item",
|
||||
{"parent": invoice, "item_code": sle.item_code, "sales_order": sr.sales_order},
|
||||
{
|
||||
"parent": invoice,
|
||||
"item_code": sle.item_code,
|
||||
"sales_order": sr.sales_order,
|
||||
},
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
@@ -467,7 +484,9 @@ def validate_serial_no(sle, item_det):
|
||||
{"parent": sales_invoice, "item_code": sle.item_code},
|
||||
"sales_order",
|
||||
)
|
||||
if sales_order and get_reserved_qty_for_so(sales_order, sle.item_code):
|
||||
if sales_order and get_reserved_qty_for_so(
|
||||
sales_order, sle.item_code
|
||||
):
|
||||
validate_so_serial_no(sr, sales_order)
|
||||
elif cint(sle.actual_qty) < 0:
|
||||
# transfer out
|
||||
@@ -483,9 +502,7 @@ def validate_serial_no(sle, item_det):
|
||||
|
||||
|
||||
def check_serial_no_validity_on_cancel(serial_no, sle):
|
||||
sr = frappe.db.get_value(
|
||||
"Serial No", serial_no, ["name", "warehouse", "company", "status"], as_dict=1
|
||||
)
|
||||
sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse", "company", "status"], as_dict=1)
|
||||
sr_link = frappe.utils.get_link_to_form("Serial No", serial_no)
|
||||
doc_link = frappe.utils.get_link_to_form(sle.voucher_type, sle.voucher_no)
|
||||
actual_qty = cint(sle.actual_qty)
|
||||
@@ -538,9 +555,7 @@ def validate_so_serial_no(sr, sales_order):
|
||||
|
||||
|
||||
def has_serial_no_exists(sn, sle):
|
||||
if (
|
||||
sn.warehouse and not sle.skip_serial_no_validaiton and sle.voucher_type != "Stock Reconciliation"
|
||||
):
|
||||
if sn.warehouse and not sle.skip_serial_no_validaiton and sle.voucher_type != "Stock Reconciliation":
|
||||
return True
|
||||
|
||||
if sn.company != sle.company:
|
||||
@@ -584,7 +599,7 @@ def update_serial_nos(sle, item_det):
|
||||
|
||||
def get_auto_serial_nos(serial_no_series, qty):
|
||||
serial_nos = []
|
||||
for i in range(cint(qty)):
|
||||
for _i in range(cint(qty)):
|
||||
serial_nos.append(get_new_serial_number(serial_no_series))
|
||||
|
||||
return "\n".join(serial_nos)
|
||||
@@ -634,13 +649,11 @@ def auto_make_serial_nos(args):
|
||||
|
||||
def get_items_html(serial_nos, item_code):
|
||||
body = ", ".join(serial_nos)
|
||||
return """<details><summary>
|
||||
<b>{0}:</b> {1} Serial Numbers <span class="caret"></span>
|
||||
return f"""<details><summary>
|
||||
<b>{item_code}:</b> {len(serial_nos)} Serial Numbers <span class="caret"></span>
|
||||
</summary>
|
||||
<div class="small">{2}</div></details>
|
||||
""".format(
|
||||
item_code, len(serial_nos), body
|
||||
)
|
||||
<div class="small">{body}</div></details>
|
||||
"""
|
||||
|
||||
|
||||
def get_item_details(item_code):
|
||||
@@ -657,9 +670,7 @@ def get_serial_nos(serial_no):
|
||||
if isinstance(serial_no, list):
|
||||
return serial_no
|
||||
|
||||
return [
|
||||
s.strip() for s in cstr(serial_no).strip().upper().replace(",", "\n").split("\n") if s.strip()
|
||||
]
|
||||
return [s.strip() for s in cstr(serial_no).strip().upper().replace(",", "\n").split("\n") if s.strip()]
|
||||
|
||||
|
||||
def clean_serial_no_string(serial_no: str) -> str:
|
||||
@@ -779,11 +790,9 @@ def update_maintenance_status():
|
||||
def get_delivery_note_serial_no(item_code, qty, delivery_note):
|
||||
serial_nos = ""
|
||||
dn_serial_nos = frappe.db.sql_list(
|
||||
""" select name from `tabSerial No`
|
||||
f""" select name from `tabSerial No`
|
||||
where item_code = %(item_code)s and delivery_document_no = %(delivery_note)s
|
||||
and sales_invoice is null limit {0}""".format(
|
||||
cint(qty)
|
||||
),
|
||||
and sales_invoice is null limit {cint(qty)}""",
|
||||
{"item_code": item_code, "delivery_note": delivery_note},
|
||||
)
|
||||
|
||||
@@ -798,12 +807,11 @@ def auto_fetch_serial_number(
|
||||
qty: int,
|
||||
item_code: str,
|
||||
warehouse: str,
|
||||
posting_date: Optional[str] = None,
|
||||
batch_nos: Optional[Union[str, List[str]]] = None,
|
||||
for_doctype: Optional[str] = None,
|
||||
posting_date: str | None = None,
|
||||
batch_nos: str | list[str] | None = None,
|
||||
for_doctype: str | None = None,
|
||||
exclude_sr_nos=None,
|
||||
) -> List[str]:
|
||||
|
||||
) -> list[str]:
|
||||
filters = frappe._dict({"item_code": item_code, "warehouse": warehouse})
|
||||
|
||||
if exclude_sr_nos is None:
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
frappe.listview_settings['Serial No'] = {
|
||||
frappe.listview_settings["Serial No"] = {
|
||||
add_fields: ["item_code", "warehouse", "warranty_expiry_date", "delivery_document_type"],
|
||||
get_indicator: (doc) => {
|
||||
if (doc.delivery_document_type) {
|
||||
return [__("Delivered"), "green", "delivery_document_type,is,set"];
|
||||
} else if (doc.warranty_expiry_date && frappe.datetime.get_diff(doc.warranty_expiry_date, frappe.datetime.nowdate()) <= 0) {
|
||||
return [__("Expired"), "red", "warranty_expiry_date,not in,|warranty_expiry_date,<=,Today|delivery_document_type,is,not set"];
|
||||
} else if (
|
||||
doc.warranty_expiry_date &&
|
||||
frappe.datetime.get_diff(doc.warranty_expiry_date, frappe.datetime.nowdate()) <= 0
|
||||
) {
|
||||
return [
|
||||
__("Expired"),
|
||||
"red",
|
||||
"warranty_expiry_date,not in,|warranty_expiry_date,<=,Today|delivery_document_type,is,not set",
|
||||
];
|
||||
} else if (!doc.warehouse) {
|
||||
return [__("Inactive"), "grey", "warehouse,is,not set"];
|
||||
} else {
|
||||
return [__("Active"), "green", "delivery_document_type,is,not set"];
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -248,16 +248,12 @@ class TestSerialNo(FrappeTestCase):
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
serial_nos = ["LOWVALUATION", "HIGHVALUATION"]
|
||||
|
||||
in1 = make_stock_entry(
|
||||
item_code=item_code, to_warehouse=warehouse, qty=1, rate=42, serial_no=serial_nos[0]
|
||||
)
|
||||
in2 = make_stock_entry(
|
||||
make_stock_entry(item_code=item_code, to_warehouse=warehouse, qty=1, rate=42, serial_no=serial_nos[0])
|
||||
make_stock_entry(
|
||||
item_code=item_code, to_warehouse=warehouse, qty=1, rate=113, serial_no=serial_nos[1]
|
||||
)
|
||||
|
||||
out = create_delivery_note(
|
||||
item_code=item_code, qty=1, serial_no=serial_nos[0], do_not_submit=True
|
||||
)
|
||||
out = create_delivery_note(item_code=item_code, qty=1, serial_no=serial_nos[0], do_not_submit=True)
|
||||
|
||||
# change serial no
|
||||
out.items[0].serial_no = serial_nos[1]
|
||||
|
||||
@@ -1,34 +1,44 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Shipment', {
|
||||
address_query: function(frm, link_doctype, link_name, is_your_company_address) {
|
||||
frappe.ui.form.on("Shipment", {
|
||||
address_query: function (frm, link_doctype, link_name, is_your_company_address) {
|
||||
return {
|
||||
query: 'frappe.contacts.doctype.address.address.address_query',
|
||||
query: "frappe.contacts.doctype.address.address.address_query",
|
||||
filters: {
|
||||
link_doctype: link_doctype,
|
||||
link_name: link_name,
|
||||
is_your_company_address: is_your_company_address
|
||||
}
|
||||
is_your_company_address: is_your_company_address,
|
||||
},
|
||||
};
|
||||
},
|
||||
contact_query: function(frm, link_doctype, link_name) {
|
||||
contact_query: function (frm, link_doctype, link_name) {
|
||||
return {
|
||||
query: 'frappe.contacts.doctype.contact.contact.contact_query',
|
||||
query: "frappe.contacts.doctype.contact.contact.contact_query",
|
||||
filters: {
|
||||
link_doctype: link_doctype,
|
||||
link_name: link_name
|
||||
}
|
||||
link_name: link_name,
|
||||
},
|
||||
};
|
||||
},
|
||||
onload: function(frm) {
|
||||
onload: function (frm) {
|
||||
frm.set_query("delivery_address_name", () => {
|
||||
let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}`;
|
||||
return frm.events.address_query(frm, frm.doc.delivery_to_type, frm.doc[delivery_to], frm.doc.delivery_to_type === 'Company' ? 1 : 0);
|
||||
return frm.events.address_query(
|
||||
frm,
|
||||
frm.doc.delivery_to_type,
|
||||
frm.doc[delivery_to],
|
||||
frm.doc.delivery_to_type === "Company" ? 1 : 0
|
||||
);
|
||||
});
|
||||
frm.set_query("pickup_address_name", () => {
|
||||
let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}`;
|
||||
return frm.events.address_query(frm, frm.doc.pickup_from_type, frm.doc[pickup_from], frm.doc.pickup_from_type === 'Company' ? 1 : 0);
|
||||
return frm.events.address_query(
|
||||
frm,
|
||||
frm.doc.pickup_from_type,
|
||||
frm.doc[pickup_from],
|
||||
frm.doc.pickup_from_type === "Company" ? 1 : 0
|
||||
);
|
||||
});
|
||||
frm.set_query("delivery_contact_name", () => {
|
||||
let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}`;
|
||||
@@ -38,8 +48,8 @@ frappe.ui.form.on('Shipment', {
|
||||
let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}`;
|
||||
return frm.events.contact_query(frm, frm.doc.pickup_from_type, frm.doc[pickup_from]);
|
||||
});
|
||||
frm.set_query("delivery_note", "shipment_delivery_note", function() {
|
||||
let customer = '';
|
||||
frm.set_query("delivery_note", "shipment_delivery_note", function () {
|
||||
let customer = "";
|
||||
if (frm.doc.delivery_to_type == "Customer") {
|
||||
customer = frm.doc.delivery_customer;
|
||||
}
|
||||
@@ -51,305 +61,329 @@ frappe.ui.form.on('Shipment', {
|
||||
filters: {
|
||||
customer: customer,
|
||||
docstatus: 1,
|
||||
status: ["not in", ["Cancelled"]]
|
||||
}
|
||||
status: ["not in", ["Cancelled"]],
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
refresh: function() {
|
||||
$('div[data-fieldname=pickup_address] > div > .clearfix').hide();
|
||||
$('div[data-fieldname=pickup_contact] > div > .clearfix').hide();
|
||||
$('div[data-fieldname=delivery_address] > div > .clearfix').hide();
|
||||
$('div[data-fieldname=delivery_contact] > div > .clearfix').hide();
|
||||
refresh: function () {
|
||||
$("div[data-fieldname=pickup_address] > div > .clearfix").hide();
|
||||
$("div[data-fieldname=pickup_contact] > div > .clearfix").hide();
|
||||
$("div[data-fieldname=delivery_address] > div > .clearfix").hide();
|
||||
$("div[data-fieldname=delivery_contact] > div > .clearfix").hide();
|
||||
},
|
||||
before_save: function(frm) {
|
||||
before_save: function (frm) {
|
||||
let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}`;
|
||||
frm.set_value("delivery_to", frm.doc[delivery_to]);
|
||||
let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}`;
|
||||
frm.set_value("pickup", frm.doc[pickup_from]);
|
||||
},
|
||||
set_pickup_company_address: function(frm) {
|
||||
frappe.db.get_value('Address', {
|
||||
address_title: frm.doc.pickup_company,
|
||||
is_your_company_address: 1
|
||||
}, 'name', (r) => {
|
||||
frm.set_value("pickup_address_name", r.name);
|
||||
});
|
||||
set_pickup_company_address: function (frm) {
|
||||
frappe.db.get_value(
|
||||
"Address",
|
||||
{
|
||||
address_title: frm.doc.pickup_company,
|
||||
is_your_company_address: 1,
|
||||
},
|
||||
"name",
|
||||
(r) => {
|
||||
frm.set_value("pickup_address_name", r.name);
|
||||
}
|
||||
);
|
||||
},
|
||||
set_delivery_company_address: function(frm) {
|
||||
frappe.db.get_value('Address', {
|
||||
address_title: frm.doc.delivery_company,
|
||||
is_your_company_address: 1
|
||||
}, 'name', (r) => {
|
||||
frm.set_value("delivery_address_name", r.name);
|
||||
});
|
||||
set_delivery_company_address: function (frm) {
|
||||
frappe.db.get_value(
|
||||
"Address",
|
||||
{
|
||||
address_title: frm.doc.delivery_company,
|
||||
is_your_company_address: 1,
|
||||
},
|
||||
"name",
|
||||
(r) => {
|
||||
frm.set_value("delivery_address_name", r.name);
|
||||
}
|
||||
);
|
||||
},
|
||||
pickup_from_type: function(frm) {
|
||||
if (frm.doc.pickup_from_type == 'Company') {
|
||||
frm.set_value("pickup_company", frappe.defaults.get_default('company'));
|
||||
frm.set_value("pickup_customer", '');
|
||||
frm.set_value("pickup_supplier", '');
|
||||
pickup_from_type: function (frm) {
|
||||
if (frm.doc.pickup_from_type == "Company") {
|
||||
frm.set_value("pickup_company", frappe.defaults.get_default("company"));
|
||||
frm.set_value("pickup_customer", "");
|
||||
frm.set_value("pickup_supplier", "");
|
||||
} else {
|
||||
frm.trigger('clear_pickup_fields');
|
||||
frm.trigger("clear_pickup_fields");
|
||||
}
|
||||
if (frm.doc.pickup_from_type == 'Customer') {
|
||||
frm.set_value("pickup_company", '');
|
||||
frm.set_value("pickup_supplier", '');
|
||||
if (frm.doc.pickup_from_type == "Customer") {
|
||||
frm.set_value("pickup_company", "");
|
||||
frm.set_value("pickup_supplier", "");
|
||||
}
|
||||
if (frm.doc.pickup_from_type == 'Supplier') {
|
||||
frm.set_value("pickup_customer", '');
|
||||
frm.set_value("pickup_company", '');
|
||||
if (frm.doc.pickup_from_type == "Supplier") {
|
||||
frm.set_value("pickup_customer", "");
|
||||
frm.set_value("pickup_company", "");
|
||||
}
|
||||
},
|
||||
delivery_to_type: function(frm) {
|
||||
if (frm.doc.delivery_to_type == 'Company') {
|
||||
frm.set_value("delivery_company", frappe.defaults.get_default('company'));
|
||||
frm.set_value("delivery_customer", '');
|
||||
frm.set_value("delivery_supplier", '');
|
||||
delivery_to_type: function (frm) {
|
||||
if (frm.doc.delivery_to_type == "Company") {
|
||||
frm.set_value("delivery_company", frappe.defaults.get_default("company"));
|
||||
frm.set_value("delivery_customer", "");
|
||||
frm.set_value("delivery_supplier", "");
|
||||
} else {
|
||||
frm.trigger('clear_delivery_fields');
|
||||
frm.trigger("clear_delivery_fields");
|
||||
}
|
||||
if (frm.doc.delivery_to_type == 'Customer') {
|
||||
frm.set_value("delivery_company", '');
|
||||
frm.set_value("delivery_supplier", '');
|
||||
if (frm.doc.delivery_to_type == "Customer") {
|
||||
frm.set_value("delivery_company", "");
|
||||
frm.set_value("delivery_supplier", "");
|
||||
}
|
||||
if (frm.doc.delivery_to_type == 'Supplier') {
|
||||
frm.set_value("delivery_customer", '');
|
||||
frm.set_value("delivery_company", '');
|
||||
if (frm.doc.delivery_to_type == "Supplier") {
|
||||
frm.set_value("delivery_customer", "");
|
||||
frm.set_value("delivery_company", "");
|
||||
frm.toggle_display("shipment_delivery_note", false);
|
||||
} else {
|
||||
frm.toggle_display("shipment_delivery_note", true);
|
||||
}
|
||||
},
|
||||
delivery_address_name: function(frm) {
|
||||
if (frm.doc.delivery_to_type == 'Company') {
|
||||
erpnext.utils.get_address_display(frm, 'delivery_address_name', 'delivery_address', true);
|
||||
delivery_address_name: function (frm) {
|
||||
if (frm.doc.delivery_to_type == "Company") {
|
||||
erpnext.utils.get_address_display(frm, "delivery_address_name", "delivery_address", true);
|
||||
} else {
|
||||
erpnext.utils.get_address_display(frm, 'delivery_address_name', 'delivery_address', false);
|
||||
erpnext.utils.get_address_display(frm, "delivery_address_name", "delivery_address", false);
|
||||
}
|
||||
},
|
||||
pickup_address_name: function(frm) {
|
||||
if (frm.doc.pickup_from_type == 'Company') {
|
||||
erpnext.utils.get_address_display(frm, 'pickup_address_name', 'pickup_address', true);
|
||||
pickup_address_name: function (frm) {
|
||||
if (frm.doc.pickup_from_type == "Company") {
|
||||
erpnext.utils.get_address_display(frm, "pickup_address_name", "pickup_address", true);
|
||||
} else {
|
||||
erpnext.utils.get_address_display(frm, 'pickup_address_name', 'pickup_address', false);
|
||||
erpnext.utils.get_address_display(frm, "pickup_address_name", "pickup_address", false);
|
||||
}
|
||||
},
|
||||
get_contact_display: function(frm, contact_name, contact_type) {
|
||||
get_contact_display: function (frm, contact_name, contact_type) {
|
||||
frappe.call({
|
||||
method: "frappe.contacts.doctype.contact.contact.get_contact_details",
|
||||
args: { contact: contact_name },
|
||||
callback: function(r) {
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
if (!(r.message.contact_email && (r.message.contact_phone || r.message.contact_mobile))) {
|
||||
if (contact_type == 'Delivery') {
|
||||
frm.set_value('delivery_contact_name', '');
|
||||
frm.set_value('delivery_contact', '');
|
||||
if (contact_type == "Delivery") {
|
||||
frm.set_value("delivery_contact_name", "");
|
||||
frm.set_value("delivery_contact", "");
|
||||
} else {
|
||||
frm.set_value('pickup_contact_name', '');
|
||||
frm.set_value('pickup_contact', '');
|
||||
frm.set_value("pickup_contact_name", "");
|
||||
frm.set_value("pickup_contact", "");
|
||||
}
|
||||
frappe.throw(__("Email or Phone/Mobile of the Contact are mandatory to continue.")
|
||||
+ "</br>" + __("Please set Email/Phone for the contact")
|
||||
+ ` <a href='/app/contact/${contact_name}'>${contact_name}</a>`);
|
||||
frappe.throw(
|
||||
__("Email or Phone/Mobile of the Contact are mandatory to continue.") +
|
||||
"</br>" +
|
||||
__("Please set Email/Phone for the contact") +
|
||||
` <a href='/app/contact/${contact_name}'>${contact_name}</a>`
|
||||
);
|
||||
}
|
||||
let contact_display = r.message.contact_display;
|
||||
if (r.message.contact_email) {
|
||||
contact_display += '<br>' + r.message.contact_email;
|
||||
contact_display += "<br>" + r.message.contact_email;
|
||||
}
|
||||
if (r.message.contact_phone) {
|
||||
contact_display += '<br>' + r.message.contact_phone;
|
||||
contact_display += "<br>" + r.message.contact_phone;
|
||||
}
|
||||
if (r.message.contact_mobile && !r.message.contact_phone) {
|
||||
contact_display += '<br>' + r.message.contact_mobile;
|
||||
contact_display += "<br>" + r.message.contact_mobile;
|
||||
}
|
||||
if (contact_type == 'Delivery') {
|
||||
frm.set_value('delivery_contact', contact_display);
|
||||
if (contact_type == "Delivery") {
|
||||
frm.set_value("delivery_contact", contact_display);
|
||||
if (r.message.contact_email) {
|
||||
frm.set_value('delivery_contact_email', r.message.contact_email);
|
||||
frm.set_value("delivery_contact_email", r.message.contact_email);
|
||||
}
|
||||
} else {
|
||||
frm.set_value('pickup_contact', contact_display);
|
||||
frm.set_value("pickup_contact", contact_display);
|
||||
if (r.message.contact_email) {
|
||||
frm.set_value('pickup_contact_email', r.message.contact_email);
|
||||
frm.set_value("pickup_contact_email", r.message.contact_email);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
delivery_contact_name: function(frm) {
|
||||
delivery_contact_name: function (frm) {
|
||||
if (frm.doc.delivery_contact_name) {
|
||||
frm.events.get_contact_display(frm, frm.doc.delivery_contact_name, 'Delivery');
|
||||
frm.events.get_contact_display(frm, frm.doc.delivery_contact_name, "Delivery");
|
||||
}
|
||||
},
|
||||
pickup_contact_name: function(frm) {
|
||||
pickup_contact_name: function (frm) {
|
||||
if (frm.doc.pickup_contact_name) {
|
||||
frm.events.get_contact_display(frm, frm.doc.pickup_contact_name, 'Pickup');
|
||||
frm.events.get_contact_display(frm, frm.doc.pickup_contact_name, "Pickup");
|
||||
}
|
||||
},
|
||||
pickup_contact_person: function(frm) {
|
||||
pickup_contact_person: function (frm) {
|
||||
if (frm.doc.pickup_contact_person) {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.shipment.shipment.get_company_contact",
|
||||
args: { user: frm.doc.pickup_contact_person },
|
||||
callback: function({ message }) {
|
||||
callback: function ({ message }) {
|
||||
const r = message;
|
||||
let contact_display = `${r.first_name} ${r.last_name}`;
|
||||
if (r.email) {
|
||||
contact_display += `<br>${ r.email }`;
|
||||
frm.set_value('pickup_contact_email', r.email);
|
||||
contact_display += `<br>${r.email}`;
|
||||
frm.set_value("pickup_contact_email", r.email);
|
||||
}
|
||||
if (r.phone) {
|
||||
contact_display += `<br>${ r.phone }`;
|
||||
contact_display += `<br>${r.phone}`;
|
||||
}
|
||||
if (r.mobile_no && !r.phone) {
|
||||
contact_display += `<br>${ r.mobile_no }`;
|
||||
contact_display += `<br>${r.mobile_no}`;
|
||||
}
|
||||
frm.set_value('pickup_contact', contact_display);
|
||||
}
|
||||
frm.set_value("pickup_contact", contact_display);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
if (frm.doc.pickup_from_type === 'Company') {
|
||||
if (frm.doc.pickup_from_type === "Company") {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.shipment.shipment.get_company_contact",
|
||||
args: { user: frappe.session.user },
|
||||
callback: function({ message }) {
|
||||
callback: function ({ message }) {
|
||||
const r = message;
|
||||
let contact_display = `${r.first_name} ${r.last_name}`;
|
||||
if (r.email) {
|
||||
contact_display += `<br>${ r.email }`;
|
||||
frm.set_value('pickup_contact_email', r.email);
|
||||
contact_display += `<br>${r.email}`;
|
||||
frm.set_value("pickup_contact_email", r.email);
|
||||
}
|
||||
if (r.phone) {
|
||||
contact_display += `<br>${ r.phone }`;
|
||||
contact_display += `<br>${r.phone}`;
|
||||
}
|
||||
if (r.mobile_no && !r.phone) {
|
||||
contact_display += `<br>${ r.mobile_no }`;
|
||||
contact_display += `<br>${r.mobile_no}`;
|
||||
}
|
||||
frm.set_value('pickup_contact', contact_display);
|
||||
}
|
||||
frm.set_value("pickup_contact", contact_display);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
set_company_contact: function(frm, delivery_type) {
|
||||
frappe.db.get_value('User', { name: frappe.session.user }, ['full_name', 'last_name', 'email', 'phone', 'mobile_no'], (r) => {
|
||||
if (!(r.last_name && r.email && (r.phone || r.mobile_no))) {
|
||||
if (delivery_type == 'Delivery') {
|
||||
frm.set_value('delivery_company', '');
|
||||
frm.set_value('delivery_contact', '');
|
||||
set_company_contact: function (frm, delivery_type) {
|
||||
frappe.db.get_value(
|
||||
"User",
|
||||
{ name: frappe.session.user },
|
||||
["full_name", "last_name", "email", "phone", "mobile_no"],
|
||||
(r) => {
|
||||
if (!(r.last_name && r.email && (r.phone || r.mobile_no))) {
|
||||
if (delivery_type == "Delivery") {
|
||||
frm.set_value("delivery_company", "");
|
||||
frm.set_value("delivery_contact", "");
|
||||
} else {
|
||||
frm.set_value("pickup_company", "");
|
||||
frm.set_value("pickup_contact", "");
|
||||
}
|
||||
frappe.throw(
|
||||
__("Last Name, Email or Phone/Mobile of the user are mandatory to continue.") +
|
||||
"</br>" +
|
||||
__("Please first set Last Name, Email and Phone for the user") +
|
||||
` <a href="/app/user/${frappe.session.user}">${frappe.session.user}</a>`
|
||||
);
|
||||
}
|
||||
let contact_display = r.full_name;
|
||||
if (r.email) {
|
||||
contact_display += "<br>" + r.email;
|
||||
}
|
||||
if (r.phone) {
|
||||
contact_display += "<br>" + r.phone;
|
||||
}
|
||||
if (r.mobile_no && !r.phone) {
|
||||
contact_display += "<br>" + r.mobile_no;
|
||||
}
|
||||
if (delivery_type == "Delivery") {
|
||||
frm.set_value("delivery_contact", contact_display);
|
||||
if (r.email) {
|
||||
frm.set_value("delivery_contact_email", r.email);
|
||||
}
|
||||
} else {
|
||||
frm.set_value('pickup_company', '');
|
||||
frm.set_value('pickup_contact', '');
|
||||
}
|
||||
frappe.throw(__("Last Name, Email or Phone/Mobile of the user are mandatory to continue.") + "</br>"
|
||||
+ __("Please first set Last Name, Email and Phone for the user")
|
||||
+ ` <a href="/app/user/${frappe.session.user}">${frappe.session.user}</a>`);
|
||||
}
|
||||
let contact_display = r.full_name;
|
||||
if (r.email) {
|
||||
contact_display += '<br>' + r.email;
|
||||
}
|
||||
if (r.phone) {
|
||||
contact_display += '<br>' + r.phone;
|
||||
}
|
||||
if (r.mobile_no && !r.phone) {
|
||||
contact_display += '<br>' + r.mobile_no;
|
||||
}
|
||||
if (delivery_type == 'Delivery') {
|
||||
frm.set_value('delivery_contact', contact_display);
|
||||
if (r.email) {
|
||||
frm.set_value('delivery_contact_email', r.email);
|
||||
}
|
||||
} else {
|
||||
frm.set_value('pickup_contact', contact_display);
|
||||
if (r.email) {
|
||||
frm.set_value('pickup_contact_email', r.email);
|
||||
frm.set_value("pickup_contact", contact_display);
|
||||
if (r.email) {
|
||||
frm.set_value("pickup_contact_email", r.email);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
frm.set_value('pickup_contact_person', frappe.session.user);
|
||||
);
|
||||
frm.set_value("pickup_contact_person", frappe.session.user);
|
||||
},
|
||||
pickup_company: function(frm) {
|
||||
if (frm.doc.pickup_from_type == 'Company' && frm.doc.pickup_company) {
|
||||
frm.trigger('set_pickup_company_address');
|
||||
frm.events.set_company_contact(frm, 'Pickup');
|
||||
pickup_company: function (frm) {
|
||||
if (frm.doc.pickup_from_type == "Company" && frm.doc.pickup_company) {
|
||||
frm.trigger("set_pickup_company_address");
|
||||
frm.events.set_company_contact(frm, "Pickup");
|
||||
}
|
||||
},
|
||||
delivery_company: function(frm) {
|
||||
if (frm.doc.delivery_to_type == 'Company' && frm.doc.delivery_company) {
|
||||
frm.trigger('set_delivery_company_address');
|
||||
frm.events.set_company_contact(frm, 'Delivery');
|
||||
delivery_company: function (frm) {
|
||||
if (frm.doc.delivery_to_type == "Company" && frm.doc.delivery_company) {
|
||||
frm.trigger("set_delivery_company_address");
|
||||
frm.events.set_company_contact(frm, "Delivery");
|
||||
}
|
||||
},
|
||||
delivery_customer: function(frm) {
|
||||
frm.trigger('clear_delivery_fields');
|
||||
delivery_customer: function (frm) {
|
||||
frm.trigger("clear_delivery_fields");
|
||||
if (frm.doc.delivery_customer) {
|
||||
frm.events.set_address_name(frm, 'Customer', frm.doc.delivery_customer, 'Delivery');
|
||||
frm.events.set_contact_name(frm, 'Customer', frm.doc.delivery_customer, 'Delivery');
|
||||
frm.events.set_address_name(frm, "Customer", frm.doc.delivery_customer, "Delivery");
|
||||
frm.events.set_contact_name(frm, "Customer", frm.doc.delivery_customer, "Delivery");
|
||||
}
|
||||
},
|
||||
delivery_supplier: function(frm) {
|
||||
frm.trigger('clear_delivery_fields');
|
||||
delivery_supplier: function (frm) {
|
||||
frm.trigger("clear_delivery_fields");
|
||||
if (frm.doc.delivery_supplier) {
|
||||
frm.events.set_address_name(frm, 'Supplier', frm.doc.delivery_supplier, 'Delivery');
|
||||
frm.events.set_contact_name(frm, 'Supplier', frm.doc.delivery_supplier, 'Delivery');
|
||||
frm.events.set_address_name(frm, "Supplier", frm.doc.delivery_supplier, "Delivery");
|
||||
frm.events.set_contact_name(frm, "Supplier", frm.doc.delivery_supplier, "Delivery");
|
||||
}
|
||||
},
|
||||
pickup_customer: function(frm) {
|
||||
pickup_customer: function (frm) {
|
||||
if (frm.doc.pickup_customer) {
|
||||
frm.events.set_address_name(frm, 'Customer', frm.doc.pickup_customer, 'Pickup');
|
||||
frm.events.set_contact_name(frm, 'Customer', frm.doc.pickup_customer, 'Pickup');
|
||||
frm.events.set_address_name(frm, "Customer", frm.doc.pickup_customer, "Pickup");
|
||||
frm.events.set_contact_name(frm, "Customer", frm.doc.pickup_customer, "Pickup");
|
||||
}
|
||||
},
|
||||
pickup_supplier: function(frm) {
|
||||
pickup_supplier: function (frm) {
|
||||
if (frm.doc.pickup_supplier) {
|
||||
frm.events.set_address_name(frm, 'Supplier', frm.doc.pickup_supplier, 'Pickup');
|
||||
frm.events.set_contact_name(frm, 'Supplier', frm.doc.pickup_supplier, 'Pickup');
|
||||
frm.events.set_address_name(frm, "Supplier", frm.doc.pickup_supplier, "Pickup");
|
||||
frm.events.set_contact_name(frm, "Supplier", frm.doc.pickup_supplier, "Pickup");
|
||||
}
|
||||
},
|
||||
set_address_name: function(frm, ref_doctype, ref_docname, delivery_type) {
|
||||
set_address_name: function (frm, ref_doctype, ref_docname, delivery_type) {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.shipment.shipment.get_address_name",
|
||||
args: {
|
||||
ref_doctype: ref_doctype,
|
||||
docname: ref_docname
|
||||
docname: ref_docname,
|
||||
},
|
||||
callback: function(r) {
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
if (delivery_type == 'Delivery') {
|
||||
frm.set_value('delivery_address_name', r.message);
|
||||
if (delivery_type == "Delivery") {
|
||||
frm.set_value("delivery_address_name", r.message);
|
||||
} else {
|
||||
frm.set_value('pickup_address_name', r.message);
|
||||
frm.set_value("pickup_address_name", r.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
set_contact_name: function(frm, ref_doctype, ref_docname, delivery_type) {
|
||||
set_contact_name: function (frm, ref_doctype, ref_docname, delivery_type) {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.shipment.shipment.get_contact_name",
|
||||
args: {
|
||||
ref_doctype: ref_doctype,
|
||||
docname: ref_docname
|
||||
docname: ref_docname,
|
||||
},
|
||||
callback: function(r) {
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
if (delivery_type == 'Delivery') {
|
||||
frm.set_value('delivery_contact_name', r.message);
|
||||
if (delivery_type == "Delivery") {
|
||||
frm.set_value("delivery_contact_name", r.message);
|
||||
} else {
|
||||
frm.set_value('pickup_contact_name', r.message);
|
||||
frm.set_value("pickup_contact_name", r.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
add_template: function(frm) {
|
||||
add_template: function (frm) {
|
||||
if (frm.doc.parcel_template) {
|
||||
frappe.model.with_doc("Shipment Parcel Template", frm.doc.parcel_template, () => {
|
||||
let parcel_template = frappe.model.get_doc("Shipment Parcel Template", frm.doc.parcel_template);
|
||||
let parcel_template = frappe.model.get_doc(
|
||||
"Shipment Parcel Template",
|
||||
frm.doc.parcel_template
|
||||
);
|
||||
let row = frappe.model.add_child(frm.doc, "Shipment Parcel", "shipment_parcel");
|
||||
row.length = parcel_template.length;
|
||||
row.width = parcel_template.width;
|
||||
@@ -359,56 +393,71 @@ frappe.ui.form.on('Shipment', {
|
||||
});
|
||||
}
|
||||
},
|
||||
pickup_date: function(frm) {
|
||||
pickup_date: function (frm) {
|
||||
if (frm.doc.pickup_date < frappe.datetime.get_today()) {
|
||||
frappe.throw(__("Pickup Date cannot be before this day"));
|
||||
}
|
||||
},
|
||||
clear_pickup_fields: function(frm) {
|
||||
let fields = ["pickup_address_name", "pickup_contact_name", "pickup_address", "pickup_contact", "pickup_contact_email", "pickup_contact_person"];
|
||||
clear_pickup_fields: function (frm) {
|
||||
let fields = [
|
||||
"pickup_address_name",
|
||||
"pickup_contact_name",
|
||||
"pickup_address",
|
||||
"pickup_contact",
|
||||
"pickup_contact_email",
|
||||
"pickup_contact_person",
|
||||
];
|
||||
for (let field of fields) {
|
||||
frm.set_value(field, '');
|
||||
frm.set_value(field, "");
|
||||
}
|
||||
},
|
||||
clear_delivery_fields: function(frm) {
|
||||
let fields = ["delivery_address_name", "delivery_contact_name", "delivery_address", "delivery_contact", "delivery_contact_email"];
|
||||
clear_delivery_fields: function (frm) {
|
||||
let fields = [
|
||||
"delivery_address_name",
|
||||
"delivery_contact_name",
|
||||
"delivery_address",
|
||||
"delivery_contact",
|
||||
"delivery_contact_email",
|
||||
];
|
||||
for (let field of fields) {
|
||||
frm.set_value(field, '');
|
||||
frm.set_value(field, "");
|
||||
}
|
||||
},
|
||||
remove_email_row: function(frm, table, fieldname) {
|
||||
$.each(frm.doc[table] || [], function(i, detail) {
|
||||
remove_email_row: function (frm, table, fieldname) {
|
||||
$.each(frm.doc[table] || [], function (i, detail) {
|
||||
if (detail.email === fieldname) {
|
||||
cur_frm.get_field(table).grid.grid_rows[i].remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Shipment Delivery Note', {
|
||||
delivery_note: function(frm, cdt, cdn) {
|
||||
frappe.ui.form.on("Shipment Delivery Note", {
|
||||
delivery_note: function (frm, cdt, cdn) {
|
||||
let row = locals[cdt][cdn];
|
||||
if (row.delivery_note) {
|
||||
let row_index = row.idx - 1;
|
||||
if (validate_duplicate(frm, 'shipment_delivery_note', row.delivery_note, row_index)) {
|
||||
frappe.throw(__("You have entered a duplicate Delivery Note on Row") + ` ${row.idx}. ` + __("Please rectify and try again."));
|
||||
if (validate_duplicate(frm, "shipment_delivery_note", row.delivery_note, row_index)) {
|
||||
frappe.throw(
|
||||
__("You have entered a duplicate Delivery Note on Row") +
|
||||
` ${row.idx}. ` +
|
||||
__("Please rectify and try again.")
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
grand_total: function(frm, cdt, cdn) {
|
||||
grand_total: function (frm, cdt, cdn) {
|
||||
let row = locals[cdt][cdn];
|
||||
if (row.grand_total) {
|
||||
var value_of_goods = parseFloat(frm.doc.value_of_goods)+parseFloat(row.grand_total);
|
||||
var value_of_goods = parseFloat(frm.doc.value_of_goods) + parseFloat(row.grand_total);
|
||||
frm.set_value("value_of_goods", Math.round(value_of_goods));
|
||||
frm.refresh_fields("value_of_goods");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
var validate_duplicate = function(frm, table, fieldname, index) {
|
||||
return (
|
||||
table === 'shipment_delivery_note'
|
||||
? frm.doc[table].some((detail, i) => detail.delivery_note === fieldname && !(index === i))
|
||||
: frm.doc[table].some((detail, i) => detail.email === fieldname && !(index === i))
|
||||
);
|
||||
var validate_duplicate = function (frm, table, fieldname, index) {
|
||||
return table === "shipment_delivery_note"
|
||||
? frm.doc[table].some((detail, i) => detail.delivery_note === fieldname && !(index === i))
|
||||
: frm.doc[table].some((detail, i) => detail.email === fieldname && !(index === i));
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
frappe.listview_settings['Shipment'] = {
|
||||
frappe.listview_settings["Shipment"] = {
|
||||
add_fields: ["status"],
|
||||
get_indicator: function(doc) {
|
||||
if (doc.status=='Booked') {
|
||||
get_indicator: function (doc) {
|
||||
if (doc.status == "Booked") {
|
||||
return [__("Booked"), "green"];
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -76,9 +76,7 @@ def create_test_shipment(delivery_notes=None):
|
||||
shipment.description_of_content = "unit test entry"
|
||||
for delivery_note in delivery_notes:
|
||||
shipment.append("shipment_delivery_note", {"delivery_note": delivery_note.name})
|
||||
shipment.append(
|
||||
"shipment_parcel", {"length": 5, "width": 5, "height": 5, "weight": 5, "count": 5}
|
||||
)
|
||||
shipment.append("shipment_parcel", {"length": 5, "width": 5, "height": 5, "weight": 5, "count": 5})
|
||||
shipment.insert()
|
||||
return shipment
|
||||
|
||||
@@ -96,9 +94,7 @@ def get_shipment_customer_contact(customer_name):
|
||||
|
||||
def get_shipment_customer_address(customer_name):
|
||||
address_title = customer_name + " address 123"
|
||||
customer_address = frappe.get_all(
|
||||
"Address", fields=["name"], filters={"address_title": address_title}
|
||||
)
|
||||
customer_address = frappe.get_all("Address", fields=["name"], filters={"address_title": address_title})
|
||||
if len(customer_address):
|
||||
return customer_address[0]
|
||||
else:
|
||||
@@ -160,9 +156,7 @@ def create_customer_contact(fname, lname):
|
||||
customer.is_primary_contact = 1
|
||||
customer.is_billing_contact = 1
|
||||
customer.append("email_ids", {"email_id": "randomme@email.com", "is_primary": 1})
|
||||
customer.append(
|
||||
"phone_nos", {"phone": "123123123", "is_primary_phone": 1, "is_primary_mobile_no": 1}
|
||||
)
|
||||
customer.append("phone_nos", {"phone": "123123123", "is_primary_phone": 1, "is_primary_mobile_no": 1})
|
||||
customer.status = "Passive"
|
||||
customer.insert()
|
||||
return customer
|
||||
|
||||
@@ -16,22 +16,19 @@
|
||||
"fieldname": "length",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Length (cm)",
|
||||
"reqd": 1
|
||||
"label": "Length (cm)"
|
||||
},
|
||||
{
|
||||
"fieldname": "width",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Width (cm)",
|
||||
"reqd": 1
|
||||
"label": "Width (cm)"
|
||||
},
|
||||
{
|
||||
"fieldname": "height",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Height (cm)",
|
||||
"reqd": 1
|
||||
"label": "Height (cm)"
|
||||
},
|
||||
{
|
||||
"fieldname": "weight",
|
||||
@@ -52,7 +49,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-09 12:54:14.847170",
|
||||
"modified": "2024-03-06 16:48:57.355757",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Shipment Parcel",
|
||||
@@ -61,5 +58,6 @@
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Shipment Parcel Template', {
|
||||
frappe.ui.form.on("Shipment Parcel Template", {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
|
||||
@@ -517,7 +517,12 @@ frappe.ui.form.on('Stock Entry', {
|
||||
},
|
||||
callback: function(r) {
|
||||
if (!r.exc) {
|
||||
["actual_qty", "basic_rate"].forEach((field) => {
|
||||
let fields = ["actual_qty", "basic_rate"];
|
||||
if (frm.doc.purpose == "Material Receipt") {
|
||||
fields = ["actual_qty"];
|
||||
}
|
||||
|
||||
fields.forEach((field) => {
|
||||
frappe.model.set_value(cdt, cdn, field, (r.message[field] || 0.0));
|
||||
});
|
||||
frm.events.calculate_basic_amount(frm, child);
|
||||
@@ -543,7 +548,9 @@ frappe.ui.form.on('Stock Entry', {
|
||||
|
||||
let fields = [
|
||||
{"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
|
||||
options:"BOM", reqd: 1, get_query: filters()},
|
||||
options:"BOM", reqd: 1, get_query: () => {
|
||||
return {filters: { docstatus:1, "is_active": 1 }};
|
||||
}},
|
||||
{"fieldname":"source_warehouse", "fieldtype":"Link", "label":__("Source Warehouse"),
|
||||
options:"Warehouse"},
|
||||
{"fieldname":"target_warehouse", "fieldtype":"Link", "label":__("Target Warehouse"),
|
||||
|
||||
@@ -104,7 +104,8 @@
|
||||
"in_standard_filter": 1,
|
||||
"label": "Stock Entry Type",
|
||||
"options": "Stock Entry Type",
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.purpose == 'Material Transfer'",
|
||||
@@ -546,7 +547,8 @@
|
||||
"label": "Job Card",
|
||||
"options": "Job Card",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
@@ -679,7 +681,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-19 18:23:40.748114",
|
||||
"modified": "2024-01-12 11:56:58.644882",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry",
|
||||
|
||||
@@ -9,22 +9,17 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import (
|
||||
cint,
|
||||
comma_or,
|
||||
cstr,
|
||||
flt,
|
||||
format_time,
|
||||
formatdate,
|
||||
getdate,
|
||||
month_diff,
|
||||
nowdate,
|
||||
)
|
||||
from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.general_ledger import process_gl_map
|
||||
from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
|
||||
from erpnext.manufacturing.doctype.bom.bom import add_additional_cost, validate_bom_no
|
||||
from erpnext.manufacturing.doctype.bom.bom import (
|
||||
add_additional_cost,
|
||||
get_op_cost_from_sub_assemblies,
|
||||
get_scrap_items_from_sub_assemblies,
|
||||
validate_bom_no,
|
||||
)
|
||||
from erpnext.setup.doctype.brand.brand import get_brand_defaults
|
||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||
from erpnext.stock.doctype.batch.batch import get_batch_no, get_batch_qty, set_batch_nos
|
||||
@@ -37,6 +32,7 @@ from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import (
|
||||
OpeningEntryAccountError,
|
||||
)
|
||||
from erpnext.stock.get_item_details import (
|
||||
get_barcode_data,
|
||||
get_bin_details,
|
||||
get_conversion_factor,
|
||||
get_default_cost_center,
|
||||
@@ -73,7 +69,7 @@ form_grid_templates = {"items": "templates/form_grid/stock_entry_grid.html"}
|
||||
|
||||
class StockEntry(StockController):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(StockEntry, self).__init__(*args, **kwargs)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.purchase_order:
|
||||
self.subcontract_data = frappe._dict(
|
||||
{
|
||||
@@ -103,9 +99,7 @@ class StockEntry(StockController):
|
||||
def before_validate(self):
|
||||
from erpnext.stock.doctype.putaway_rule.putaway_rule import apply_putaway_rule
|
||||
|
||||
apply_rule = self.apply_putaway_rule and (
|
||||
self.purpose in ["Material Transfer", "Material Receipt"]
|
||||
)
|
||||
apply_rule = self.apply_putaway_rule and (self.purpose in ["Material Transfer", "Material Receipt"])
|
||||
|
||||
if self.get("items") and apply_rule:
|
||||
apply_putaway_rule(self.doctype, self.get("items"), self.company, purpose=self.purpose)
|
||||
@@ -154,7 +148,6 @@ class StockEntry(StockController):
|
||||
set_batch_nos(self, "s_warehouse")
|
||||
|
||||
self.validate_serialized_batch()
|
||||
self.set_actual_qty()
|
||||
self.calculate_rate_and_amount()
|
||||
self.validate_putaway_capacity()
|
||||
|
||||
@@ -164,41 +157,6 @@ class StockEntry(StockController):
|
||||
self.reset_default_field_value("from_warehouse", "items", "s_warehouse")
|
||||
self.reset_default_field_value("to_warehouse", "items", "t_warehouse")
|
||||
|
||||
def submit(self):
|
||||
if self.is_enqueue_action():
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Entry and revert to the Draft stage"
|
||||
)
|
||||
)
|
||||
self.queue_action("submit", timeout=2000)
|
||||
else:
|
||||
self._submit()
|
||||
|
||||
def cancel(self):
|
||||
if self.is_enqueue_action():
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Entry and revert to the Submitted stage"
|
||||
)
|
||||
)
|
||||
self.queue_action("cancel", timeout=2000)
|
||||
else:
|
||||
self._cancel()
|
||||
|
||||
def is_enqueue_action(self, force=False) -> bool:
|
||||
if force:
|
||||
return True
|
||||
|
||||
if frappe.flags.in_test:
|
||||
return False
|
||||
|
||||
# If line items are more than 100 or record is older than 6 months
|
||||
if len(self.items) > 50 or month_diff(nowdate(), self.posting_date) > 6:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def on_submit(self):
|
||||
self.update_stock_ledger()
|
||||
|
||||
@@ -309,7 +267,11 @@ class StockEntry(StockController):
|
||||
if self.purpose == "Send to Warehouse":
|
||||
for d in frappe.get_all(
|
||||
"Stock Entry",
|
||||
filters={"docstatus": 0, "outgoing_stock_entry": self.name, "purpose": "Receive at Warehouse"},
|
||||
filters={
|
||||
"docstatus": 0,
|
||||
"outgoing_stock_entry": self.name,
|
||||
"purpose": "Receive at Warehouse",
|
||||
},
|
||||
):
|
||||
frappe.delete_doc("Stock Entry", d.name)
|
||||
|
||||
@@ -393,7 +355,14 @@ class StockEntry(StockController):
|
||||
for field in reset_fields:
|
||||
item.set(field, item_details.get(field))
|
||||
|
||||
update_fields = ("uom", "description", "expense_account", "cost_center", "conversion_factor")
|
||||
update_fields = (
|
||||
"uom",
|
||||
"description",
|
||||
"expense_account",
|
||||
"cost_center",
|
||||
"conversion_factor",
|
||||
"barcode",
|
||||
)
|
||||
|
||||
for field in update_fields:
|
||||
if not item.get(field):
|
||||
@@ -463,7 +432,7 @@ class StockEntry(StockController):
|
||||
return
|
||||
|
||||
precision = frappe.get_precision("Stock Entry Detail", "qty")
|
||||
fg_item = list(fg_qty.keys())[0]
|
||||
fg_item = next(iter(fg_qty.keys()))
|
||||
fg_item_qty = flt(fg_qty[fg_item], precision)
|
||||
fg_completed_qty = flt(self.fg_completed_qty, precision)
|
||||
|
||||
@@ -646,14 +615,13 @@ class StockEntry(StockController):
|
||||
production_item, qty = frappe.db.get_value(
|
||||
"Work Order", self.work_order, ["production_item", "qty"]
|
||||
)
|
||||
args = other_ste + [production_item]
|
||||
args = [*other_ste, production_item]
|
||||
fg_qty_already_entered = frappe.db.sql(
|
||||
"""select sum(transfer_qty)
|
||||
from `tabStock Entry Detail`
|
||||
where parent in (%s)
|
||||
and item_code = %s
|
||||
and ifnull(s_warehouse,'')='' """
|
||||
% (", ".join(["%s" * len(other_ste)]), "%s"),
|
||||
where parent in ({})
|
||||
and item_code = {}
|
||||
and ifnull(s_warehouse,'')='' """.format(", ".join(["%s" * len(other_ste)]), "%s"),
|
||||
args,
|
||||
)[0][0]
|
||||
if fg_qty_already_entered and fg_qty_already_entered >= qty:
|
||||
@@ -731,9 +699,7 @@ class StockEntry(StockController):
|
||||
Set rate for outgoing, scrapped and finished items
|
||||
"""
|
||||
# Set rate for outgoing items
|
||||
outgoing_items_cost = self.set_rate_for_outgoing_items(
|
||||
reset_outgoing_rate, raise_error_if_no_rate
|
||||
)
|
||||
outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate, raise_error_if_no_rate)
|
||||
finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item)
|
||||
|
||||
items = []
|
||||
@@ -897,8 +863,6 @@ class StockEntry(StockController):
|
||||
self.purpose = frappe.get_cached_value("Stock Entry Type", self.stock_entry_type, "purpose")
|
||||
|
||||
def validate_duplicate_serial_no(self):
|
||||
warehouse_wise_serial_nos = {}
|
||||
|
||||
# In case of repack the source and target serial nos could be same
|
||||
for warehouse in ["s_warehouse", "t_warehouse"]:
|
||||
serial_nos = []
|
||||
@@ -936,13 +900,17 @@ class StockEntry(StockController):
|
||||
item_code = se_item.original_item or se_item.item_code
|
||||
precision = cint(frappe.db.get_default("float_precision")) or 3
|
||||
required_qty = sum(
|
||||
[flt(d.required_qty) for d in subcontract_order.supplied_items if d.rm_item_code == item_code]
|
||||
[
|
||||
flt(d.required_qty)
|
||||
for d in subcontract_order.supplied_items
|
||||
if d.rm_item_code == item_code
|
||||
]
|
||||
)
|
||||
|
||||
total_allowed = required_qty + (required_qty * (qty_allowance / 100))
|
||||
|
||||
if not required_qty:
|
||||
bom_no = frappe.db.get_value(
|
||||
frappe.db.get_value(
|
||||
f"{self.subcontract_data.order_doctype} Item",
|
||||
{
|
||||
"parent": self.get(self.subcontract_data.order_field),
|
||||
@@ -988,7 +956,10 @@ class StockEntry(StockController):
|
||||
& (se.docstatus == 1)
|
||||
& (se_detail.item_code == se_item.item_code)
|
||||
& (
|
||||
((se.purchase_order == self.purchase_order) & (se_detail.po_detail == se_item.po_detail))
|
||||
(
|
||||
(se.purchase_order == self.purchase_order)
|
||||
& (se_detail.po_detail == se_item.po_detail)
|
||||
)
|
||||
if self.subcontract_data.order_doctype == "Purchase Order"
|
||||
else (
|
||||
(se.subcontracting_order == self.subcontracting_order)
|
||||
@@ -1041,7 +1012,9 @@ class StockEntry(StockController):
|
||||
else:
|
||||
if not se_item.allow_alternative_item:
|
||||
frappe.throw(
|
||||
_("Row {0}# Item {1} not found in 'Raw Materials Supplied' table in {2} {3}").format(
|
||||
_(
|
||||
"Row {0}# Item {1} not found in 'Raw Materials Supplied' table in {2} {3}"
|
||||
).format(
|
||||
se_item.idx,
|
||||
se_item.item_code,
|
||||
self.subcontract_data.order_doctype,
|
||||
@@ -1228,12 +1201,18 @@ class StockEntry(StockController):
|
||||
for d in self.get("items"):
|
||||
if cstr(d.s_warehouse):
|
||||
sle = self.get_sl_entries(
|
||||
d, {"warehouse": cstr(d.s_warehouse), "actual_qty": -flt(d.transfer_qty), "incoming_rate": 0}
|
||||
d,
|
||||
{
|
||||
"warehouse": cstr(d.s_warehouse),
|
||||
"actual_qty": -flt(d.transfer_qty),
|
||||
"incoming_rate": 0,
|
||||
},
|
||||
)
|
||||
if cstr(d.t_warehouse):
|
||||
sle.dependant_sle_voucher_detail_no = d.name
|
||||
elif finished_item_row and (
|
||||
finished_item_row.item_code != d.item_code or finished_item_row.t_warehouse != d.s_warehouse
|
||||
finished_item_row.item_code != d.item_code
|
||||
or finished_item_row.t_warehouse != d.s_warehouse
|
||||
):
|
||||
sle.dependant_sle_voucher_detail_no = finished_item_row.name
|
||||
|
||||
@@ -1256,7 +1235,7 @@ class StockEntry(StockController):
|
||||
sl_entries.append(sle)
|
||||
|
||||
def get_gl_entries(self, warehouse_account):
|
||||
gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account)
|
||||
gl_entries = super().get_gl_entries(warehouse_account)
|
||||
|
||||
if self.purpose in ("Repack", "Manufacture"):
|
||||
total_basic_amount = sum(flt(t.basic_amount) for t in self.get("items") if t.is_finished_item)
|
||||
@@ -1289,9 +1268,9 @@ class StockEntry(StockController):
|
||||
flt(t.amount * multiply_based_on) / divide_based_on
|
||||
)
|
||||
|
||||
item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["base_amount"] += (
|
||||
flt(t.base_amount * multiply_based_on) / divide_based_on
|
||||
)
|
||||
item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account][
|
||||
"base_amount"
|
||||
] += flt(t.base_amount * multiply_based_on) / divide_based_on
|
||||
|
||||
if item_account_wise_additional_cost:
|
||||
for d in self.get("items"):
|
||||
@@ -1323,7 +1302,9 @@ class StockEntry(StockController):
|
||||
"cost_center": d.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": -1
|
||||
* amount["base_amount"], # put it as negative credit instead of debit purposefully
|
||||
* amount[
|
||||
"base_amount"
|
||||
], # put it as negative credit instead of debit purposefully
|
||||
},
|
||||
item=d,
|
||||
)
|
||||
@@ -1421,11 +1402,7 @@ class StockEntry(StockController):
|
||||
ret.update(get_uom_details(args.get("item_code"), args.get("uom"), args.get("qty")))
|
||||
|
||||
if self.purpose == "Material Issue":
|
||||
ret["expense_account"] = (
|
||||
item.get("expense_account")
|
||||
or item_group_defaults.get("expense_account")
|
||||
or frappe.get_cached_value("Company", self.company, "default_expense_account")
|
||||
)
|
||||
ret["expense_account"] = item.get("expense_account") or item_group_defaults.get("expense_account")
|
||||
|
||||
for company_field, field in {
|
||||
"stock_adjustment_account": "expense_account",
|
||||
@@ -1456,13 +1433,20 @@ class StockEntry(StockController):
|
||||
):
|
||||
subcontract_items = frappe.get_all(
|
||||
self.subcontract_data.order_supplied_items_field,
|
||||
{"parent": self.get(self.subcontract_data.order_field), "rm_item_code": args.get("item_code")},
|
||||
{
|
||||
"parent": self.get(self.subcontract_data.order_field),
|
||||
"rm_item_code": args.get("item_code"),
|
||||
},
|
||||
"main_item_code",
|
||||
)
|
||||
|
||||
if subcontract_items and len(subcontract_items) == 1:
|
||||
ret["subcontracted_item"] = subcontract_items[0].main_item_code
|
||||
|
||||
barcode_data = get_barcode_data(item_code=item.name)
|
||||
if barcode_data and len(barcode_data.get(item.name)) == 1:
|
||||
ret["barcode"] = barcode_data.get(item.name)[0]
|
||||
|
||||
return ret
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -1506,7 +1490,6 @@ class StockEntry(StockController):
|
||||
)
|
||||
|
||||
if self.bom_no:
|
||||
|
||||
backflush_based_on = frappe.db.get_single_value(
|
||||
"Manufacturing Settings", "backflush_raw_materials_based_on"
|
||||
)
|
||||
@@ -1520,7 +1503,6 @@ class StockEntry(StockController):
|
||||
"Material Transfer for Manufacture",
|
||||
"Material Consumption for Manufacture",
|
||||
]:
|
||||
|
||||
if self.work_order and self.purpose == "Material Transfer for Manufacture":
|
||||
item_dict = self.get_pending_raw_materials(backflush_based_on)
|
||||
if self.to_warehouse and self.pro_doc:
|
||||
@@ -1530,7 +1512,10 @@ class StockEntry(StockController):
|
||||
|
||||
elif (
|
||||
self.work_order
|
||||
and (self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture")
|
||||
and (
|
||||
self.purpose == "Manufacture"
|
||||
or self.purpose == "Material Consumption for Manufacture"
|
||||
)
|
||||
and not self.pro_doc.skip_transfer
|
||||
and self.flags.backflush_based_on == "Material Transferred for Manufacture"
|
||||
):
|
||||
@@ -1538,7 +1523,10 @@ class StockEntry(StockController):
|
||||
|
||||
elif (
|
||||
self.work_order
|
||||
and (self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture")
|
||||
and (
|
||||
self.purpose == "Manufacture"
|
||||
or self.purpose == "Material Consumption for Manufacture"
|
||||
)
|
||||
and self.flags.backflush_based_on == "BOM"
|
||||
and frappe.db.get_single_value("Manufacturing Settings", "material_consumption") == 1
|
||||
):
|
||||
@@ -1551,7 +1539,10 @@ class StockEntry(StockController):
|
||||
item_dict = self.get_bom_raw_materials(self.fg_completed_qty)
|
||||
|
||||
# Get Subcontract Order Supplied Items Details
|
||||
if self.get(self.subcontract_data.order_field) and self.purpose == "Send to Subcontractor":
|
||||
if (
|
||||
self.get(self.subcontract_data.order_field)
|
||||
and self.purpose == "Send to Subcontractor"
|
||||
):
|
||||
# Get Subcontract Order Supplied Items Details
|
||||
parent = frappe.qb.DocType(self.subcontract_data.order_doctype)
|
||||
child = frappe.qb.DocType(self.subcontract_data.order_supplied_items_field)
|
||||
@@ -1570,9 +1561,14 @@ class StockEntry(StockController):
|
||||
if self.pro_doc and cint(self.pro_doc.from_wip_warehouse):
|
||||
item["from_warehouse"] = self.pro_doc.wip_warehouse
|
||||
# Get Reserve Warehouse from Subcontract Order
|
||||
if self.get(self.subcontract_data.order_field) and self.purpose == "Send to Subcontractor":
|
||||
if (
|
||||
self.get(self.subcontract_data.order_field)
|
||||
and self.purpose == "Send to Subcontractor"
|
||||
):
|
||||
item["from_warehouse"] = item_wh.get(item.item_code)
|
||||
item["to_warehouse"] = self.to_warehouse if self.purpose == "Send to Subcontractor" else ""
|
||||
item["to_warehouse"] = (
|
||||
self.to_warehouse if self.purpose == "Send to Subcontractor" else ""
|
||||
)
|
||||
|
||||
self.add_to_stock_entry_detail(item_dict)
|
||||
|
||||
@@ -1767,11 +1763,20 @@ class StockEntry(StockController):
|
||||
def get_bom_scrap_material(self, qty):
|
||||
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
|
||||
|
||||
# item dict = { item_code: {qty, description, stock_uom} }
|
||||
item_dict = (
|
||||
get_bom_items_as_dict(self.bom_no, self.company, qty=qty, fetch_exploded=0, fetch_scrap_items=1)
|
||||
or {}
|
||||
)
|
||||
if (
|
||||
frappe.db.get_single_value("Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies")
|
||||
and self.work_order
|
||||
and frappe.get_cached_value("Work Order", self.work_order, "use_multi_level_bom")
|
||||
):
|
||||
item_dict = get_scrap_items_from_sub_assemblies(self.bom_no, self.company, qty)
|
||||
else:
|
||||
# item dict = { item_code: {qty, description, stock_uom} }
|
||||
item_dict = (
|
||||
get_bom_items_as_dict(
|
||||
self.bom_no, self.company, qty=qty, fetch_exploded=0, fetch_scrap_items=1
|
||||
)
|
||||
or {}
|
||||
)
|
||||
|
||||
for item in item_dict.values():
|
||||
item.from_warehouse = ""
|
||||
@@ -1915,7 +1920,7 @@ class StockEntry(StockController):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
for key, row in available_materials.items():
|
||||
for _key, row in available_materials.items():
|
||||
remaining_qty_to_produce = flt(wo_data.trans_qty) - flt(wo_data.produced_qty)
|
||||
if remaining_qty_to_produce <= 0 and not self.is_return:
|
||||
continue
|
||||
@@ -2059,9 +2064,7 @@ class StockEntry(StockController):
|
||||
continue
|
||||
|
||||
transfer_pending = flt(d.required_qty) > flt(d.transferred_qty)
|
||||
can_transfer = transfer_pending or (
|
||||
backflush_based_on == "Material Transferred for Manufacture"
|
||||
)
|
||||
can_transfer = transfer_pending or (backflush_based_on == "Material Transferred for Manufacture")
|
||||
|
||||
if not can_transfer:
|
||||
continue
|
||||
@@ -2076,7 +2079,9 @@ class StockEntry(StockController):
|
||||
)
|
||||
item_row["job_card_item"] = job_card_item or None
|
||||
|
||||
if d.source_warehouse and not frappe.db.get_value("Warehouse", d.source_warehouse, "is_group"):
|
||||
if d.source_warehouse and not frappe.db.get_value(
|
||||
"Warehouse", d.source_warehouse, "is_group"
|
||||
):
|
||||
item_row["from_warehouse"] = d.source_warehouse
|
||||
|
||||
item_row["to_warehouse"] = wip_warehouse
|
||||
@@ -2131,9 +2136,9 @@ class StockEntry(StockController):
|
||||
if item_row.get(field):
|
||||
se_child.set(field, item_row.get(field))
|
||||
|
||||
if se_child.s_warehouse == None:
|
||||
if se_child.s_warehouse is None:
|
||||
se_child.s_warehouse = self.from_warehouse
|
||||
if se_child.t_warehouse == None:
|
||||
if se_child.t_warehouse is None:
|
||||
se_child.t_warehouse = self.to_warehouse
|
||||
|
||||
# in stock uom
|
||||
@@ -2189,15 +2194,20 @@ class StockEntry(StockController):
|
||||
expiry_date = frappe.db.get_value("Batch", item.batch_no, "expiry_date")
|
||||
if expiry_date:
|
||||
if getdate(self.posting_date) > getdate(expiry_date):
|
||||
frappe.throw(_("Batch {0} of Item {1} has expired.").format(item.batch_no, item.item_code))
|
||||
frappe.throw(
|
||||
_("Batch {0} of Item {1} has expired.").format(
|
||||
item.batch_no, item.item_code
|
||||
)
|
||||
)
|
||||
else:
|
||||
frappe.throw(_("Batch {0} of Item {1} is disabled.").format(item.batch_no, item.item_code))
|
||||
frappe.throw(
|
||||
_("Batch {0} of Item {1} is disabled.").format(item.batch_no, item.item_code)
|
||||
)
|
||||
|
||||
def update_subcontract_order_supplied_items(self):
|
||||
if self.get(self.subcontract_data.order_field) and (
|
||||
self.purpose in ["Send to Subcontractor", "Material Transfer"] or self.is_return
|
||||
):
|
||||
|
||||
# Get Subcontract Order Supplied Items Details
|
||||
order_supplied_items = frappe.db.get_all(
|
||||
self.subcontract_data.order_supplied_items_field,
|
||||
@@ -2298,8 +2308,8 @@ class StockEntry(StockController):
|
||||
|
||||
cond = ""
|
||||
for data, transferred_qty in stock_entries.items():
|
||||
cond += """ WHEN (parent = %s and name = %s) THEN %s
|
||||
""" % (
|
||||
cond += """ WHEN (parent = {} and name = {}) THEN {}
|
||||
""".format(
|
||||
frappe.db.escape(data[0]),
|
||||
frappe.db.escape(data[1]),
|
||||
transferred_qty,
|
||||
@@ -2355,7 +2365,9 @@ class StockEntry(StockController):
|
||||
material_request = item.material_request or None
|
||||
if self.purpose == "Material Transfer" and material_request not in material_requests:
|
||||
if self.outgoing_stock_entry and parent_se:
|
||||
material_request = frappe.get_value("Stock Entry Detail", item.ste_detail, "material_request")
|
||||
material_request = frappe.get_value(
|
||||
"Stock Entry Detail", item.ste_detail, "material_request"
|
||||
)
|
||||
|
||||
if material_request and material_request not in material_requests:
|
||||
material_requests.append(material_request)
|
||||
@@ -2527,6 +2539,15 @@ def get_work_order_details(work_order, company):
|
||||
def get_operating_cost_per_unit(work_order=None, bom_no=None):
|
||||
operating_cost_per_unit = 0
|
||||
if work_order:
|
||||
if (
|
||||
bom_no
|
||||
and frappe.db.get_single_value(
|
||||
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies"
|
||||
)
|
||||
and frappe.get_cached_value("Work Order", work_order, "use_multi_level_bom")
|
||||
):
|
||||
return get_op_cost_from_sub_assemblies(bom_no)
|
||||
|
||||
if not bom_no:
|
||||
bom_no = work_order.bom_no
|
||||
|
||||
@@ -2551,9 +2572,7 @@ def get_operating_cost_per_unit(work_order=None, bom_no=None):
|
||||
)
|
||||
)
|
||||
):
|
||||
operating_cost_per_unit += flt(work_order.corrective_operation_cost) / flt(
|
||||
work_order.produced_qty
|
||||
)
|
||||
operating_cost_per_unit += flt(work_order.corrective_operation_cost) / flt(work_order.produced_qty)
|
||||
|
||||
return operating_cost_per_unit
|
||||
|
||||
@@ -2566,24 +2585,20 @@ def get_used_alternative_items(
|
||||
if subcontract_order:
|
||||
cond = f"and ste.purpose = 'Send to Subcontractor' and ste.{subcontract_order_field} = '{subcontract_order}'"
|
||||
elif work_order:
|
||||
cond = "and ste.purpose = 'Material Transfer for Manufacture' and ste.work_order = '{0}'".format(
|
||||
work_order
|
||||
)
|
||||
cond = f"and ste.purpose = 'Material Transfer for Manufacture' and ste.work_order = '{work_order}'"
|
||||
|
||||
if not cond:
|
||||
return {}
|
||||
|
||||
used_alternative_items = {}
|
||||
data = frappe.db.sql(
|
||||
""" select sted.original_item, sted.uom, sted.conversion_factor,
|
||||
f""" select sted.original_item, sted.uom, sted.conversion_factor,
|
||||
sted.item_code, sted.item_name, sted.conversion_factor,sted.stock_uom, sted.description
|
||||
from
|
||||
`tabStock Entry` ste, `tabStock Entry Detail` sted
|
||||
where
|
||||
sted.parent = ste.name and ste.docstatus = 1 and sted.original_item != sted.item_code
|
||||
{0} """.format(
|
||||
cond
|
||||
),
|
||||
{cond} """,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
@@ -2621,9 +2636,7 @@ def get_uom_details(item_code, uom, qty):
|
||||
conversion_factor = get_conversion_factor(item_code, uom).get("conversion_factor")
|
||||
|
||||
if not conversion_factor:
|
||||
frappe.msgprint(
|
||||
_("UOM conversion factor required for UOM: {0} in Item: {1}").format(uom, item_code)
|
||||
)
|
||||
frappe.msgprint(_("UOM conversion factor required for UOM: {0} in Item: {1}").format(uom, item_code))
|
||||
ret = {"uom": ""}
|
||||
else:
|
||||
ret = {
|
||||
@@ -2733,9 +2746,7 @@ def get_supplied_items(
|
||||
else:
|
||||
supplied_item.supplied_qty += row.transfer_qty
|
||||
|
||||
supplied_item.total_supplied_qty = flt(supplied_item.supplied_qty) - flt(
|
||||
supplied_item.returned_qty
|
||||
)
|
||||
supplied_item.total_supplied_qty = flt(supplied_item.supplied_qty) - flt(supplied_item.returned_qty)
|
||||
|
||||
return supplied_item_details
|
||||
|
||||
@@ -2825,7 +2836,11 @@ def get_stock_entry_data(work_order):
|
||||
& (stock_entry_detail.s_warehouse.isnotnull())
|
||||
& (
|
||||
stock_entry.purpose.isin(
|
||||
["Manufacture", "Material Consumption for Manufacture", "Material Transfer for Manufacture"]
|
||||
[
|
||||
"Manufacture",
|
||||
"Material Consumption for Manufacture",
|
||||
"Material Transfer for Manufacture",
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user