You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

514 lines
15 KiB

  1. // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. // License: GNU General Public License v3. See license.txt
  3. cur_frm.cscript.tax_table = "Sales Taxes and Charges";
  4. {% include 'erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js' %}
  5. cur_frm.email_field = "contact_email";
  6. frappe.provide("erpnext.selling");
  7. erpnext.selling.SellingController = class SellingController extends erpnext.TransactionController {
  8. setup() {
  9. super.setup();
  10. }
  11. onload() {
  12. super.onload();
  13. this.setup_queries();
  14. this.frm.set_query('shipping_rule', function() {
  15. return {
  16. filters: {
  17. "shipping_rule_type": "Selling"
  18. }
  19. };
  20. });
  21. }
  22. setup_queries() {
  23. var me = this;
  24. $.each([["customer", "customer"],
  25. ["lead", "lead"]],
  26. function(i, opts) {
  27. if(me.frm.fields_dict[opts[0]])
  28. me.frm.set_query(opts[0], erpnext.queries[opts[1]]);
  29. });
  30. me.frm.set_query('contact_person', erpnext.queries.contact_query);
  31. me.frm.set_query('customer_address', erpnext.queries.address_query);
  32. me.frm.set_query('shipping_address_name', erpnext.queries.address_query);
  33. me.frm.set_query('dispatch_address_name', erpnext.queries.dispatch_address_query);
  34. erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype);
  35. if(this.frm.fields_dict.selling_price_list) {
  36. this.frm.set_query("selling_price_list", function() {
  37. return { filters: { selling: 1 } };
  38. });
  39. }
  40. if(this.frm.fields_dict.tc_name) {
  41. this.frm.set_query("tc_name", function() {
  42. return { filters: { selling: 1 } };
  43. });
  44. }
  45. if(!this.frm.fields_dict["items"]) {
  46. return;
  47. }
  48. if(this.frm.fields_dict["items"].grid.get_field('item_code')) {
  49. this.frm.set_query("item_code", "items", function() {
  50. return {
  51. query: "erpnext.controllers.queries.item_query",
  52. filters: {'is_sales_item': 1, 'customer': cur_frm.doc.customer, 'has_variants': 0}
  53. }
  54. });
  55. }
  56. if(this.frm.fields_dict["packed_items"] &&
  57. this.frm.fields_dict["packed_items"].grid.get_field('batch_no')) {
  58. this.frm.set_query("batch_no", "packed_items", function(doc, cdt, cdn) {
  59. return me.set_query_for_batch(doc, cdt, cdn)
  60. });
  61. }
  62. if(this.frm.fields_dict["items"].grid.get_field('item_code')) {
  63. this.frm.set_query("item_tax_template", "items", function(doc, cdt, cdn) {
  64. return me.set_query_for_item_tax_template(doc, cdt, cdn)
  65. });
  66. }
  67. }
  68. refresh() {
  69. super.refresh();
  70. frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'}
  71. this.frm.toggle_display("customer_name",
  72. (this.frm.doc.customer_name && this.frm.doc.customer_name!==this.frm.doc.customer));
  73. this.toggle_editable_price_list_rate();
  74. }
  75. customer() {
  76. var me = this;
  77. erpnext.utils.get_party_details(this.frm, null, null, function() {
  78. me.apply_price_list();
  79. });
  80. }
  81. customer_address() {
  82. erpnext.utils.get_address_display(this.frm, "customer_address");
  83. erpnext.utils.set_taxes_from_address(this.frm, "customer_address", "customer_address", "shipping_address_name");
  84. }
  85. shipping_address_name() {
  86. erpnext.utils.get_address_display(this.frm, "shipping_address_name", "shipping_address");
  87. erpnext.utils.set_taxes_from_address(this.frm, "shipping_address_name", "customer_address", "shipping_address_name");
  88. }
  89. dispatch_address_name() {
  90. erpnext.utils.get_address_display(this.frm, "dispatch_address_name", "dispatch_address");
  91. }
  92. sales_partner() {
  93. this.apply_pricing_rule();
  94. }
  95. campaign() {
  96. this.apply_pricing_rule();
  97. }
  98. selling_price_list() {
  99. this.apply_price_list();
  100. this.set_dynamic_labels();
  101. }
  102. discount_percentage(doc, cdt, cdn) {
  103. var item = frappe.get_doc(cdt, cdn);
  104. item.discount_amount = 0.0;
  105. this.apply_discount_on_item(doc, cdt, cdn, 'discount_percentage');
  106. }
  107. discount_amount(doc, cdt, cdn) {
  108. if(doc.name === cdn) {
  109. return;
  110. }
  111. var item = frappe.get_doc(cdt, cdn);
  112. item.discount_percentage = 0.0;
  113. this.apply_discount_on_item(doc, cdt, cdn, 'discount_amount');
  114. }
  115. apply_discount_on_item(doc, cdt, cdn, field) {
  116. var item = frappe.get_doc(cdt, cdn);
  117. if(!item.price_list_rate) {
  118. item[field] = 0.0;
  119. } else {
  120. this.price_list_rate(doc, cdt, cdn);
  121. }
  122. this.set_gross_profit(item);
  123. }
  124. commission_rate() {
  125. this.calculate_commission();
  126. }
  127. total_commission() {
  128. frappe.model.round_floats_in(this.frm.doc, ["amount_eligible_for_commission", "total_commission"]);
  129. const { amount_eligible_for_commission } = this.frm.doc;
  130. if(!amount_eligible_for_commission) return;
  131. this.frm.set_value(
  132. "commission_rate", flt(
  133. this.frm.doc.total_commission * 100.0 / amount_eligible_for_commission
  134. )
  135. );
  136. }
  137. allocated_percentage(doc, cdt, cdn) {
  138. var sales_person = frappe.get_doc(cdt, cdn);
  139. if(sales_person.allocated_percentage) {
  140. sales_person.allocated_percentage = flt(sales_person.allocated_percentage,
  141. precision("allocated_percentage", sales_person));
  142. sales_person.allocated_amount = flt(this.frm.doc.amount_eligible_for_commission *
  143. sales_person.allocated_percentage / 100.0,
  144. precision("allocated_amount", sales_person));
  145. refresh_field(["allocated_amount"], sales_person);
  146. this.calculate_incentive(sales_person);
  147. refresh_field(["allocated_percentage", "allocated_amount", "commission_rate","incentives"], sales_person.name,
  148. sales_person.parentfield);
  149. }
  150. }
  151. sales_person(doc, cdt, cdn) {
  152. var row = frappe.get_doc(cdt, cdn);
  153. this.calculate_incentive(row);
  154. refresh_field("incentives",row.name,row.parentfield);
  155. }
  156. warehouse(doc, cdt, cdn) {
  157. var me = this;
  158. var item = frappe.get_doc(cdt, cdn);
  159. // check if serial nos entered are as much as qty in row
  160. if (item.serial_no) {
  161. let serial_nos = item.serial_no.split(`\n`).filter(sn => sn.trim()); // filter out whitespaces
  162. if (item.qty === serial_nos.length) return;
  163. }
  164. if (item.serial_no && !item.batch_no) {
  165. item.serial_no = null;
  166. }
  167. var has_batch_no;
  168. frappe.db.get_value('Item', {'item_code': item.item_code}, 'has_batch_no', (r) => {
  169. has_batch_no = r && r.has_batch_no;
  170. if(item.item_code && item.warehouse) {
  171. return this.frm.call({
  172. method: "erpnext.stock.get_item_details.get_bin_details_and_serial_nos",
  173. child: item,
  174. args: {
  175. item_code: item.item_code,
  176. warehouse: item.warehouse,
  177. has_batch_no: has_batch_no || 0,
  178. stock_qty: item.stock_qty,
  179. serial_no: item.serial_no || "",
  180. },
  181. callback:function(r){
  182. if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
  183. if (doc.doctype === 'Sales Invoice' && (!doc.update_stock)) return;
  184. if (has_batch_no) {
  185. me.set_batch_number(cdt, cdn);
  186. me.batch_no(doc, cdt, cdn);
  187. }
  188. }
  189. }
  190. });
  191. }
  192. })
  193. }
  194. toggle_editable_price_list_rate() {
  195. var df = frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "price_list_rate", this.frm.doc.name);
  196. var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate"));
  197. if(df && editable_price_list_rate) {
  198. const parent_field = frappe.meta.get_parentfield(this.frm.doc.doctype, this.frm.doc.doctype + " Item");
  199. if (!this.frm.fields_dict[parent_field]) return;
  200. this.frm.fields_dict[parent_field].grid.update_docfield_property(
  201. 'price_list_rate', 'read_only', 0
  202. );
  203. }
  204. }
  205. calculate_commission() {
  206. if(!this.frm.fields_dict.commission_rate || this.frm.doc.docstatus === 1) return;
  207. if(this.frm.doc.commission_rate > 100) {
  208. this.frm.set_value("commission_rate", 100);
  209. frappe.throw(`${__(frappe.meta.get_label(
  210. this.frm.doc.doctype, "commission_rate", this.frm.doc.name
  211. ))} ${__("cannot be greater than 100")}`);
  212. }
  213. this.frm.doc.amount_eligible_for_commission = this.frm.doc.items.reduce(
  214. (sum, item) => item.grant_commission ? sum + item.base_net_amount : sum, 0
  215. )
  216. this.frm.doc.total_commission = flt(
  217. this.frm.doc.amount_eligible_for_commission * this.frm.doc.commission_rate / 100.0,
  218. precision("total_commission")
  219. );
  220. refresh_field(["amount_eligible_for_commission", "total_commission"]);
  221. }
  222. calculate_contribution() {
  223. var me = this;
  224. $.each(this.frm.doc.doctype.sales_team || [], function(i, sales_person) {
  225. frappe.model.round_floats_in(sales_person);
  226. if (!sales_person.allocated_percentage) return;
  227. sales_person.allocated_amount = flt(
  228. me.frm.doc.amount_eligible_for_commission
  229. * sales_person.allocated_percentage
  230. / 100.0,
  231. precision("allocated_amount", sales_person)
  232. );
  233. });
  234. }
  235. calculate_incentive(row) {
  236. if(row.allocated_amount)
  237. {
  238. row.incentives = flt(
  239. row.allocated_amount * row.commission_rate / 100.0,
  240. precision("incentives", row));
  241. }
  242. }
  243. batch_no(doc, cdt, cdn) {
  244. var me = this;
  245. var item = frappe.get_doc(cdt, cdn);
  246. if (item.serial_no) {
  247. return;
  248. }
  249. item.serial_no = null;
  250. var has_serial_no;
  251. frappe.db.get_value('Item', {'item_code': item.item_code}, 'has_serial_no', (r) => {
  252. has_serial_no = r && r.has_serial_no;
  253. if(item.warehouse && item.item_code && item.batch_no) {
  254. return this.frm.call({
  255. method: "erpnext.stock.get_item_details.get_batch_qty_and_serial_no",
  256. child: item,
  257. args: {
  258. "batch_no": item.batch_no,
  259. "stock_qty": item.stock_qty || item.qty, //if stock_qty field is not available fetch qty (in case of Packed Items table)
  260. "warehouse": item.warehouse,
  261. "item_code": item.item_code,
  262. "has_serial_no": has_serial_no
  263. },
  264. "fieldname": "actual_batch_qty"
  265. });
  266. }
  267. })
  268. }
  269. set_dynamic_labels() {
  270. super.set_dynamic_labels();
  271. this.set_product_bundle_help(this.frm.doc);
  272. }
  273. set_product_bundle_help(doc) {
  274. if(!cur_frm.fields_dict.packing_list) return;
  275. if ((doc.packed_items || []).length) {
  276. $(cur_frm.fields_dict.packing_list.row.wrapper).toggle(true);
  277. if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
  278. var help_msg = "<div class='alert alert-warning'>" +
  279. __("For 'Product Bundle' items, Warehouse, Serial No and Batch No will be considered from the 'Packing List' table. If Warehouse and Batch No are same for all packing items for any 'Product Bundle' item, those values can be entered in the main Item table, values will be copied to 'Packing List' table.")+
  280. "</div>";
  281. frappe.meta.get_docfield(doc.doctype, 'product_bundle_help', doc.name).options = help_msg;
  282. }
  283. } else {
  284. $(cur_frm.fields_dict.packing_list.row.wrapper).toggle(false);
  285. if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
  286. frappe.meta.get_docfield(doc.doctype, 'product_bundle_help', doc.name).options = '';
  287. }
  288. }
  289. refresh_field('product_bundle_help');
  290. }
  291. company_address() {
  292. var me = this;
  293. if(this.frm.doc.company_address) {
  294. frappe.call({
  295. method: "frappe.contacts.doctype.address.address.get_address_display",
  296. args: {"address_dict": this.frm.doc.company_address },
  297. callback: function(r) {
  298. if(r.message) {
  299. me.frm.set_value("company_address_display", r.message)
  300. }
  301. }
  302. })
  303. } else {
  304. this.frm.set_value("company_address_display", "");
  305. }
  306. }
  307. conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate) {
  308. super.conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate);
  309. if(frappe.meta.get_docfield(cdt, "stock_qty", cdn) &&
  310. in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
  311. if (doc.doctype === 'Sales Invoice' && (!doc.update_stock)) return;
  312. this.set_batch_number(cdt, cdn);
  313. }
  314. }
  315. batch_no(doc, cdt, cdn) {
  316. super.batch_no(doc, cdt, cdn);
  317. }
  318. qty(doc, cdt, cdn) {
  319. super.qty(doc, cdt, cdn);
  320. if(in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
  321. if (doc.doctype === 'Sales Invoice' && (!doc.update_stock)) return;
  322. this.set_batch_number(cdt, cdn);
  323. }
  324. }
  325. /* Determine appropriate batch number and set it in the form.
  326. * @param {string} cdt - Document Doctype
  327. * @param {string} cdn - Document name
  328. */
  329. set_batch_number(cdt, cdn) {
  330. const doc = frappe.get_doc(cdt, cdn);
  331. if (doc && doc.has_batch_no && doc.warehouse) {
  332. this._set_batch_number(doc);
  333. }
  334. }
  335. _set_batch_number(doc) {
  336. if (doc.batch_no) {
  337. return
  338. }
  339. let args = {'item_code': doc.item_code, 'warehouse': doc.warehouse, 'qty': flt(doc.qty) * flt(doc.conversion_factor)};
  340. if (doc.has_serial_no && doc.serial_no) {
  341. args['serial_no'] = doc.serial_no
  342. }
  343. return frappe.call({
  344. method: 'erpnext.stock.doctype.batch.batch.get_batch_no',
  345. args: args,
  346. callback: function(r) {
  347. if(r.message) {
  348. frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message);
  349. }
  350. }
  351. });
  352. }
  353. update_auto_repeat_reference(doc) {
  354. if (doc.auto_repeat) {
  355. frappe.call({
  356. method:"frappe.automation.doctype.auto_repeat.auto_repeat.update_reference",
  357. args:{
  358. docname: doc.auto_repeat,
  359. reference:doc.name
  360. },
  361. callback: function(r){
  362. if (r.message=="success") {
  363. frappe.show_alert({message:__("Auto repeat document updated"), indicator:'green'});
  364. } else {
  365. frappe.show_alert({message:__("An error occurred during the update process"), indicator:'red'});
  366. }
  367. }
  368. })
  369. }
  370. }
  371. };
  372. frappe.ui.form.on(cur_frm.doctype,"project", function(frm) {
  373. if(in_list(["Delivery Note", "Sales Invoice"], frm.doc.doctype)) {
  374. if(frm.doc.project) {
  375. frappe.call({
  376. method:'erpnext.projects.doctype.project.project.get_cost_center_name' ,
  377. args: { project: frm.doc.project },
  378. callback: function(r, rt) {
  379. if(!r.exc) {
  380. $.each(frm.doc["items"] || [], function(i, row) {
  381. if(r.message) {
  382. frappe.model.set_value(row.doctype, row.name, "cost_center", r.message);
  383. frappe.msgprint(__("Cost Center For Item with Item Code {0} has been Changed to {1}", [row.item_name, r.message]));
  384. }
  385. })
  386. }
  387. }
  388. })
  389. }
  390. }
  391. })
  392. frappe.ui.form.on(cur_frm.doctype, {
  393. set_as_lost_dialog: function(frm) {
  394. var dialog = new frappe.ui.Dialog({
  395. title: __("Set as Lost"),
  396. fields: [
  397. {
  398. "fieldtype": "Table MultiSelect",
  399. "label": __("Lost Reasons"),
  400. "fieldname": "lost_reason",
  401. "options": frm.doctype === 'Opportunity' ? 'Opportunity Lost Reason Detail': 'Quotation Lost Reason Detail',
  402. "reqd": 1
  403. },
  404. {
  405. "fieldtype": "Table MultiSelect",
  406. "label": __("Competitors"),
  407. "fieldname": "competitors",
  408. "options": "Competitor Detail"
  409. },
  410. {
  411. "fieldtype": "Small Text",
  412. "label": __("Detailed Reason"),
  413. "fieldname": "detailed_reason"
  414. },
  415. ],
  416. primary_action: function() {
  417. let values = dialog.get_values();
  418. frm.call({
  419. doc: frm.doc,
  420. method: 'declare_enquiry_lost',
  421. args: {
  422. 'lost_reasons_list': values.lost_reason,
  423. 'competitors': values.competitors ? values.competitors : [],
  424. 'detailed_reason': values.detailed_reason
  425. },
  426. callback: function(r) {
  427. dialog.hide();
  428. frm.reload_doc();
  429. },
  430. });
  431. },
  432. primary_action_label: __('Declare Lost')
  433. });
  434. dialog.show();
  435. }
  436. })