Multiselect dialog for getting items (#8583)

* Concatenate docnames and send to mapper

* Multiselect with checkboxes

* set setters as separate filters

* Map filter fields to target_doc

* Get items from quotation (in SO) working

* [major] Set dialog setters for all forms

* Add date field

* Specify non-default date_fields

* [minor] add test_mapper.py

* [minor] remove cur_frm

* [minor][fix] test

* [minor] fix test with make_test_records
This commit is contained in:
Prateeksha Singh 2017-05-15 11:32:06 +05:30 committed by Rushabh Mehta
parent 62e3e75555
commit edeb4dc7e0
16 changed files with 270 additions and 117 deletions

View File

@ -49,29 +49,37 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
}
if(doc.docstatus===0) {
cur_frm.add_custom_button(__('Purchase Order'), function() {
var me = this;
this.frm.add_custom_button(__('Purchase Order'), function() {
erpnext.utils.map_current_doc({
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice",
source_doctype: "Purchase Order",
target: me.frm,
setters: {
supplier: me.frm.doc.supplier || undefined,
},
get_query_filters: {
supplier: cur_frm.doc.supplier || undefined,
docstatus: 1,
status: ["!=", "Closed"],
per_billed: ["<", 99.99],
company: cur_frm.doc.company
company: me.frm.doc.company
}
})
}, __("Get items from"));
cur_frm.add_custom_button(__('Purchase Receipt'), function() {
this.frm.add_custom_button(__('Purchase Receipt'), function() {
erpnext.utils.map_current_doc({
method: "erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_purchase_invoice",
source_doctype: "Purchase Receipt",
target: me.frm,
date_field: "posting_date",
setters: {
supplier: me.frm.doc.supplier || undefined,
},
get_query_filters: {
supplier: cur_frm.doc.supplier || undefined,
docstatus: 1,
status: ["!=", "Closed"],
company: cur_frm.doc.company
company: me.frm.doc.company
}
})
}, __("Get items from"));
@ -120,7 +128,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
hide_fields(this.frm.doc);
if(cint(this.frm.doc.is_paid)) {
if(!this.frm.doc.company) {
cur_frm.set_value("is_paid", 0)
this.frm.set_value("is_paid", 0)
msgprint(__("Please specify Company to proceed"));
}
}

View File

@ -112,33 +112,43 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
},
sales_order_btn: function() {
this.$sales_order_btn = cur_frm.add_custom_button(__('Sales Order'),
var me = this;
this.$sales_order_btn = this.frm.add_custom_button(__('Sales Order'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_sales_invoice",
source_doctype: "Sales Order",
target: me.frm,
setters: {
customer: me.frm.doc.customer || undefined,
},
get_query_filters: {
docstatus: 1,
status: ["!=", "Closed"],
per_billed: ["<", 99.99],
customer: cur_frm.doc.customer || undefined,
company: cur_frm.doc.company
company: me.frm.doc.company
}
})
}, __("Get items from"));
},
delivery_note_btn: function() {
this.$delivery_note_btn = cur_frm.add_custom_button(__('Delivery Note'),
var me = this;
this.$delivery_note_btn = this.frm.add_custom_button(__('Delivery Note'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice",
source_doctype: "Delivery Note",
target: me.frm,
date_field: "posting_date",
setters: {
company: me.frm.doc.company
},
get_query: function() {
var filters = {
company: cur_frm.doc.company
docstatus: 1,
};
if(cur_frm.doc.customer) filters["customer"] = cur_frm.doc.customer;
if(me.frm.doc.customer) filters["customer"] = me.frm.doc.customer;
return {
query: "erpnext.controllers.queries.get_delivery_notes_to_be_billed",
filters: filters

View File

@ -153,30 +153,37 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
},
add_from_mappers: function() {
cur_frm.add_custom_button(__('Material Request'),
var me = this;
this.frm.add_custom_button(__('Material Request'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order",
source_doctype: "Material Request",
target: me.frm,
setters: {
company: me.frm.doc.company
},
get_query_filters: {
material_request_type: "Purchase",
docstatus: 1,
status: ["!=", "Stopped"],
per_ordered: ["<", 99.99],
company: cur_frm.doc.company
}
})
}, __("Add items from"));
cur_frm.add_custom_button(__('Supplier Quotation'),
this.frm.add_custom_button(__('Supplier Quotation'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.buying.doctype.supplier_quotation.supplier_quotation.make_purchase_order",
source_doctype: "Supplier Quotation",
target: me.frm,
setters: {
company: me.frm.doc.company
},
get_query_filters: {
docstatus: 1,
status: ["!=", "Stopped"],
company: cur_frm.doc.company
}
})
}, __("Add items from"));

View File

@ -48,7 +48,7 @@ frappe.ui.form.on("Request for Quotation",{
});
});
}
},
make_suppplier_quotation: function(frm) {
@ -124,24 +124,28 @@ frappe.ui.form.on("Request for Quotation Supplier",{
erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.extend({
refresh: function() {
var me = this;
this._super();
if (this.frm.doc.docstatus===0) {
cur_frm.add_custom_button(__('Material Request'),
this.frm.add_custom_button(__('Material Request'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.stock.doctype.material_request.material_request.make_request_for_quotation",
source_doctype: "Material Request",
target: me.frm,
setters: {
company: me.frm.doc.company
},
get_query_filters: {
material_request_type: "Purchase",
docstatus: 1,
status: ["!=", "Stopped"],
per_ordered: ["<", 99.99],
company: cur_frm.doc.company
per_ordered: ["<", 99.99]
}
})
}, __("Get items from"));
// Get items from open Material Requests based on supplier
cur_frm.add_custom_button(__('Possible Supplier'), function() {
this.frm.add_custom_button(__('Possible Supplier'), function() {
// Create a dialog window for the user to pick their supplier
var d = new frappe.ui.Dialog({
title: __('Select Possible Supplier'),
@ -150,32 +154,35 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
{fieldname: 'ok_button', fieldtype:'Button', label:'Get Items from Material Requests'},
]
});
// On the user clicking the ok button
d.fields_dict.ok_button.input.onclick = function() {
var btn = d.fields_dict.ok_button.input;
var v = d.get_values();
if(v) {
$(btn).set_working();
erpnext.utils.map_current_doc({
method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_item_from_material_requests_based_on_supplier",
source_name: v.supplier,
target: me.frm,
setters: {
company: me.frm.doc.company
},
get_query_filters: {
material_request_type: "Purchase",
docstatus: 1,
status: ["!=", "Stopped"],
per_ordered: ["<", 99.99],
company: cur_frm.doc.company
per_ordered: ["<", 99.99]
}
});
$(btn).done_working();
d.hide();
}
}
}
d.show();
}, __("Get items from"));
}
},

View File

@ -14,6 +14,7 @@ frappe.ui.form.on('Suppier Quotation', {
erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.extend({
refresh: function() {
var me = this;
this._super();
if (this.frm.doc.docstatus === 1) {
cur_frm.add_custom_button(__("Purchase Order"), this.make_purchase_order,
@ -24,18 +25,21 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext
}
else if (this.frm.doc.docstatus===0) {
cur_frm.add_custom_button(__('Material Request'),
this.frm.add_custom_button(__('Material Request'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.stock.doctype.material_request.material_request.make_supplier_quotation",
source_doctype: "Material Request",
target: me.frm,
setters: {
company: me.frm.doc.company
},
get_query_filters: {
material_request_type: "Purchase",
docstatus: 1,
status: ["!=", "Stopped"],
per_ordered: ["<", 99.99],
company: cur_frm.doc.company
per_ordered: ["<", 99.99]
}
})
}, __("Get items from"));

View File

@ -0,0 +1,73 @@
from __future__ import unicode_literals
import unittest
import frappe
import random, json
import frappe.utils
from frappe.utils import nowdate
from frappe.model import mapper
from frappe.test_runner import make_test_records
class TestMapper(unittest.TestCase):
def test_map_docs(self):
'''Test mapping of multiple source docs on a single target doc'''
make_test_records("Item")
items = frappe.get_all("Item", fields = ["name", "item_code"], filters = {'is_sales_item': 1, 'has_variants': 0})
customers = frappe.get_all("Customer")
if items and customers:
# Make source docs (quotations) and a target doc (sales order)
customer = random.choice(customers).name
qtn1, item_list_1 = self.make_quotation(items, customer)
qtn2, item_list_2 = self.make_quotation(items, customer)
so, item_list_3 = self.make_sales_order()
# Map source docs to target with corresponding mapper method
method = "erpnext.selling.doctype.quotation.quotation.make_sales_order"
updated_so = mapper.map_docs(method, json.dumps([qtn1.name, qtn2.name]), so)
# Assert that all inserted items are present in updated sales order
src_items = item_list_1 + item_list_2 + item_list_3
self.assertEqual(set([d.item_code for d in src_items]),
set([d.item_code for d in updated_so.items]))
def get_random_items(self, items, limit):
'''Get a number of random items from a list of given items'''
random_items = []
for i in range(0, limit):
random_items.append(random.choice(items))
return random_items
def make_quotation(self, items, customer):
item_list = self.get_random_items(items, 3)
qtn = frappe.get_doc({
"doctype": "Quotation",
"quotation_to": "Customer",
"customer": customer,
"order_type": "Sales"
})
for item in item_list:
qtn.append("items", {"qty": "2", "item_code": item.item_code})
qtn.submit()
return qtn, item_list
def make_sales_order(self):
item = frappe.get_doc({
"base_amount": 1000.0,
"base_rate": 100.0,
"description": "CPU",
"doctype": "Sales Order Item",
"item_code": "_Test Item Home Desktop 100",
"item_name": "CPU",
"parentfield": "items",
"qty": 10.0,
"rate": 100.0,
"warehouse": "_Test Warehouse - _TC",
"stock_uom": "_Test UOM",
"conversion_factor": 1.0,
"uom": "_Test UOM"
})
so = frappe.get_doc(frappe.get_test_records('Sales Order')[0])
so.insert(ignore_permissions=True)
return so, [item]

View File

@ -33,10 +33,13 @@ erpnext.maintenance.MaintenanceSchedule = frappe.ui.form.Controller.extend({
erpnext.utils.map_current_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_schedule",
source_doctype: "Sales Order",
target: me.frm,
setters: {
customer: me.frm.doc.customer || undefined,
order_type: me.frm.doc.order_type,
},
get_query_filters: {
docstatus: 1,
order_type: me.frm.doc.order_type,
customer: me.frm.doc.customer || undefined,
company: me.frm.doc.company
}
});

View File

@ -27,41 +27,53 @@ erpnext.maintenance.MaintenanceVisit = frappe.ui.form.Controller.extend({
refresh: function() {
frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'}
var me = this;
if (this.frm.doc.docstatus===0) {
cur_frm.add_custom_button(__('Maintenance Schedule'),
this.frm.add_custom_button(__('Maintenance Schedule'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit",
source_doctype: "Maintenance Schedule",
target: me.frm,
setters: {
customer: me.frm.doc.customer || undefined,
},
get_query_filters: {
docstatus: 1,
customer: cur_frm.doc.customer || undefined,
company: cur_frm.doc.company
company: me.frm.doc.company
}
})
}, __("Get items from"));
cur_frm.add_custom_button(__('Warranty Claim'),
this.frm.add_custom_button(__('Warranty Claim'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.support.doctype.warranty_claim.warranty_claim.make_maintenance_visit",
source_doctype: "Warranty Claim",
target: me.frm,
date_field: "complaint_date",
setters: {
customer: me.frm.doc.customer || undefined,
},
get_query_filters: {
status: ["in", "Open, Work in Progress"],
customer: cur_frm.doc.customer || undefined,
company: cur_frm.doc.company
company: me.frm.doc.company
}
})
}, __("Get items from"));
cur_frm.add_custom_button(__('Sales Order'),
this.frm.add_custom_button(__('Sales Order'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_visit",
source_doctype: "Sales Order",
target: me.frm,
setters: {
customer: me.frm.doc.customer || undefined,
},
get_query_filters: {
docstatus: 1,
order_type: cur_frm.doc.order_type,
customer: cur_frm.doc.customer || undefined,
company: cur_frm.doc.company
company: me.frm.doc.company,
order_type: me.frm.doc.order_type,
}
})
}, __("Get items from"));

View File

@ -114,63 +114,68 @@ erpnext.utils.map_current_doc = function(opts) {
}
}
var _map = function() {
// remove first item row if empty
if($.isArray(cur_frm.doc.items) && cur_frm.doc.items.length > 0) {
// remove first item row if empty
if(!cur_frm.doc.items[0].item_code) {
cur_frm.doc.items = cur_frm.doc.items.splice(1);
}
// find the doctype of the items table
var items_doctype = frappe.meta.get_docfield(cur_frm.doctype, 'items').options;
// find the link fieldname from items table for the given
// source_doctype
var link_fieldname = null;
frappe.get_meta(items_doctype).fields.forEach(function(d) {
frappe.get_meta(items_doctype).fields.forEach(function(d) {
if(d.options===opts.source_doctype) link_fieldname = d.fieldname; });
// search in existing items if the source_name is already set and full qty fetched
var already_set = false;
var item_qty_map = {};
$.each(cur_frm.doc.items, function(i, d) {
if(d[link_fieldname]==opts.source_name) {
already_set = true;
if (item_qty_map[d.item_code])
item_qty_map[d.item_code] += flt(d.qty);
else
item_qty_map[d.item_code] = flt(d.qty);
}
});
if(already_set) {
frappe.model.with_doc(opts.source_doctype, opts.source_name, function(r) {
var source_doc = frappe.model.get_doc(opts.source_doctype, opts.source_name);
$.each(source_doc.items || [], function(i, row) {
if(row.qty > flt(item_qty_map[row.item_code])) {
already_set = false;
return false;
}
})
})
if(already_set) {
frappe.msgprint(__("You have already selected items from {0} {1}",
[opts.source_doctype, opts.source_name]));
return;
}
$.each(cur_frm.doc.items, function(i, d) {
opts.source_name.forEach(function(src) {
if(d[link_fieldname]==src) {
already_set = true;
if (item_qty_map[d.item_code])
item_qty_map[d.item_code] += flt(d.qty);
else
item_qty_map[d.item_code] = flt(d.qty);
}
});
});
if(already_set) {
opts.source_name.forEach(function(src) {
frappe.model.with_doc(opts.source_doctype, src, function(r) {
var source_doc = frappe.model.get_doc(opts.source_doctype, src);
$.each(source_doc.items || [], function(i, row) {
if(row.qty > flt(item_qty_map[row.item_code])) {
already_set = false;
return false;
}
})
})
if(already_set) {
frappe.msgprint(__("You have already selected items from {0} {1}",
[opts.source_doctype, src]));
return;
}
})
}
}
return frappe.call({
// Sometimes we hit the limit for URL length of a GET request
// as we send the full target_doc. Hence this is a POST request.
type: "POST",
method: opts.method,
method: 'frappe.model.mapper.map_docs',
args: {
"source_name": opts.source_name,
"target_doc": cur_frm.doc
"method": opts.method,
"source_names": opts.source_name,
"target_doc": cur_frm.doc,
},
callback: function(r) {
if(!r.exc) {
@ -181,29 +186,26 @@ erpnext.utils.map_current_doc = function(opts) {
});
}
if(opts.source_doctype) {
var d = new frappe.ui.Dialog({
title: __("Get From ") + __(opts.source_doctype),
fields: [
{
fieldtype: "Link",
label: __(opts.source_doctype),
fieldname: opts.source_doctype,
options: opts.source_doctype,
get_query: opts.get_query,
reqd:1
},
]
var d = new frappe.ui.form.MultiSelectDialog({
doctype: opts.source_doctype,
target: opts.target,
date_field: opts.date_field || undefined,
setters: opts.setters,
get_query: opts.get_query,
action: function(selections, args) {
let values = selections;
if(values.length === 0){
frappe.msgprint(__("Please select Quotations"))
return;
}
opts.source_name = values;
opts.setters = args;
d.dialog.hide();
_map();
},
});
d.set_primary_action(__('Get Items'), function() {
var values = d.get_values();
if(!values)
return;
opts.source_name = values[opts.source_doctype];
d.hide();
_map();
})
d.show();
} else if(opts.source_name) {
opts.source_name = [opts.source_name];
_map();
}
}

View File

@ -42,18 +42,23 @@ erpnext.selling.InstallationNote = frappe.ui.form.Controller.extend({
},
refresh: function() {
var me = this;
if (this.frm.doc.docstatus===0) {
cur_frm.add_custom_button(__('From Delivery Note'),
this.frm.add_custom_button(__('From Delivery Note'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_installation_note",
source_doctype: "Delivery Note",
target: me.frm,
date_field: "posting_date",
setters: {
customer: me.frm.doc.customer || undefined,
},
get_query_filters: {
docstatus: 1,
status: ["not in", ["Stopped", "Closed"]],
per_installed: ["<", 99.99],
customer: cur_frm.doc.customer || undefined,
company: cur_frm.doc.company
company: me.frm.doc.company
}
})
}, "fa fa-download", "btn-default"

View File

@ -25,6 +25,8 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
refresh: function(doc, dt, dn) {
this._super(doc, dt, dn);
var me = this;
if(doc.docstatus == 1 && doc.status!=='Lost') {
cur_frm.add_custom_button(__('Make Sales Order'),
cur_frm.cscript['Make Sales Order']);
@ -36,17 +38,24 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
}
if (this.frm.doc.docstatus===0) {
cur_frm.add_custom_button(__('Opportunity'),
this.frm.add_custom_button(__('Opportunity'),
function() {
var setters = {};
if(me.frm.doc.customer) {
setters.customer = me.frm.doc.customer || undefined;
} else if (me.frm.doc.lead) {
setters.lead = me.frm.doc.lead || undefined;
}
erpnext.utils.map_current_doc({
method: "erpnext.crm.doctype.opportunity.opportunity.make_quotation",
source_doctype: "Opportunity",
target: me.frm,
setters: setters,
get_query_filters: {
status: ["not in", ["Lost", "Closed"]],
enquiry_type: cur_frm.doc.order_type,
customer: cur_frm.doc.customer || undefined,
lead: cur_frm.doc.lead || undefined,
company: cur_frm.doc.company
company: me.frm.doc.company,
// cannot set enquiry_type as setter, as the fieldname is order_type
enquiry_type: me.frm.doc.order_type,
}
})
}, __("Get items from"), "btn-default");

View File

@ -102,7 +102,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
target.run_method("calculate_taxes_and_totals")
def update_item(obj, target, source_parent):
target.stock_qty = flt(obj.qty) * flt(obj.conversion_factor)
target.stock_qty = flt(obj.qty) * flt(obj.conversion_factor)
doclist = get_mapped_doc("Quotation", source_name, {
"Quotation": {

View File

@ -136,12 +136,15 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
erpnext.utils.map_current_doc({
method: "erpnext.selling.doctype.quotation.quotation.make_sales_order",
source_doctype: "Quotation",
target: me.frm,
setters: {
customer: me.frm.doc.customer || undefined,
order_type: me.frm.doc.order_type,
},
get_query_filters: {
company: me.frm.doc.company,
docstatus: 1,
status: ["!=", "Lost"],
order_type: me.frm.doc.order_type,
customer: me.frm.doc.customer || undefined,
company: me.frm.doc.company
}
})
}, __("Get items from"));

View File

@ -119,13 +119,16 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
erpnext.utils.map_current_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
source_doctype: "Sales Order",
target: me.frm,
setters: {
customer: me.frm.doc.customer || undefined,
},
get_query_filters: {
docstatus: 1,
status: ["!=", "Closed"],
per_delivered: ["<", 99.99],
company: me.frm.doc.company,
project: me.frm.doc.project || undefined,
customer: me.frm.doc.customer || undefined,
company: me.frm.doc.company
}
})
}, __("Get items from"));

View File

@ -95,16 +95,19 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten
}
if (this.frm.doc.docstatus===0) {
cur_frm.add_custom_button(__('Sales Order'),
this.frm.add_custom_button(__('Sales Order'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_material_request",
source_doctype: "Sales Order",
target: me.frm,
setters: {
company: me.frm.doc.company
},
get_query_filters: {
docstatus: 1,
status: ["!=", "Closed"],
per_delivered: ["<", 99.99],
company: cur_frm.doc.company
}
})
}, __("Get items from"));

View File

@ -43,6 +43,7 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
},
refresh: function() {
var me = this;
this._super();
if(this.frm.doc.docstatus===1) {
this.show_stock_ledger();
@ -53,17 +54,20 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
if(!this.frm.doc.is_return && this.frm.doc.status!="Closed") {
if(this.frm.doc.docstatus==0) {
cur_frm.add_custom_button(__('Purchase Order'),
this.frm.add_custom_button(__('Purchase Order'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt",
source_doctype: "Purchase Order",
target: me.frm,
setters: {
supplier: me.frm.doc.supplier || undefined,
},
get_query_filters: {
supplier: cur_frm.doc.supplier || undefined,
docstatus: 1,
status: ["!=", "Closed"],
per_received: ["<", 99.99],
company: cur_frm.doc.company
company: me.frm.doc.company
}
})
}, __("Get items from"));