Refactor multi pos profile selection in the pos invoice (#11721)

This commit is contained in:
rohitwaghchaure 2017-11-28 16:11:15 +05:30 committed by Nabin Hait
parent 8d9edbf133
commit c037dc775e
12 changed files with 302 additions and 163 deletions

View File

@ -2,7 +2,7 @@
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"allow_rename": 1,
"autoname": "field:pos_profile_name",
"beta": 0,
"creation": "2013-05-24 12:15:51",
@ -315,7 +315,7 @@
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -1508,7 +1508,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-10-27 06:45:32.957674",
"modified": "2017-11-24 14:08:09.184226",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",
@ -1558,6 +1558,7 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "pos_profile_name",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",

View File

@ -4,30 +4,34 @@
from __future__ import unicode_literals
import frappe
from frappe import msgprint, _
from frappe.utils import cint
from frappe.utils import cint, now
from erpnext.accounts.doctype.sales_invoice.sales_invoice import set_account_for_mode_of_payment
from frappe.model.document import Document
class POSProfile(Document):
def validate(self):
# self.check_for_duplicate()
self.validate_default_profile()
self.validate_all_link_fields()
self.validate_duplicate_groups()
self.check_default_payment()
self.validate_customer_territory_group()
def check_for_duplicate(self):
res = frappe.db.sql("""select name, user from `tabPOS Profile`
where ifnull(user, '') = %s and name != %s and company = %s and ifnull(disabled, 0) != 1""",
(self.user, self.name, self.company))
if res:
if res[0][1]:
msgprint(_("POS Profile {0} already created for user: {1} and company {2}").format(res[0][0],
res[0][1], self.company), raise_exception=1)
else:
msgprint(_("Global POS Profile {0} already created for company {1}").format(res[0][0],
self.company), raise_exception=1)
def validate_default_profile(self):
for row in self.applicable_for_users:
res = frappe.db.sql("""select pf.name
from
`tabPOS Profile User` pfu, `tabPOS Profile` pf
where
pf.name = pfu.parent and pfu.user = %s and pf.name != %s and pf.company = %s
and pfu.default=1""", (row.user, self.name, self.company))
if row.default and res:
msgprint(_("Already set default in pos profile {0} for user {1}, kindly disabled default")
.format(res[0][0], row.user), raise_exception=1)
elif not row.default and not res:
msgprint(_("Row {0}: set atleast one default pos profile for user {1}")
.format(row.idx, row.user), raise_exception=1)
def validate_all_link_fields(self):
accounts = {"Account": [self.income_account,
@ -95,44 +99,58 @@ class POSProfile(Document):
def get_series():
return frappe.get_meta("Sales Invoice").get_field("naming_series").options or ""
@frappe.whitelist()
def get_pos_profiles_for_user(user=None):
out = []
if not user:
user = frappe.session.user
def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
user = frappe.session['user']
company = filters.get('company') or frappe.defaults.get_user_default('company')
res = frappe.db.sql('''
select
parent
args = {
'user': user,
'start': start,
'company': company,
'page_len': page_len,
'txt': '%%%s%%' % txt
}
pos_profile = frappe.db.sql("""select pf.name, pf.pos_profile_name
from
`tabPOS Profile User`
`tabPOS Profile` pf, `tabPOS Profile User` pfu
where
user = %s
''', (user), as_dict=1)
pfu.parent = pf.name and pfu.user = %(user)s and pf.company = %(company)s
and (pf.name like %(txt)s or pf.pos_profile_name like %(txt)s)
and pf.disabled = 0 limit %(start)s, %(page_len)s""", args)
if not res:
company = frappe.defaults.get_user_default('company')
res = frappe.db.sql('''
select
pos_profile_name
if not pos_profile:
del args['user']
pos_profile = frappe.db.sql("""select pf.name, pf.pos_profile_name
from
`tabPOS Profile`
`tabPOS Profile` pf left join `tabPOS Profile User` pfu
on
pf.name = pfu.parent
where
company = %s
''', (company), as_dict=1)
ifnull(pfu.user, '') = '' and pf.company = %(company)s and
(pf.name like %(txt)s or pf.pos_profile_name like %(txt)s)
and pf.disabled = 0""", args)
out = [r.pos_profile_name for r in res]
return out
for r in res:
name = frappe.db.get_value('POS Profile', r.parent, 'pos_profile_name')
out.append(name)
return out
return pos_profile
@frappe.whitelist()
def get_pos_profile(pos_profile_name=None):
if not pos_profile_name: return
name = frappe.db.get_value('POS Profile', { 'pos_profile_name': pos_profile_name })
return frappe.get_doc('POS Profile', name)
def set_default_profile(pos_profile, company):
modified = now()
user = frappe.session.user
company = frappe.db.escape(company)
if pos_profile and company:
frappe.db.sql(""" update `tabPOS Profile User` pfu, `tabPOS Profile` pf
set
pfu.default = 0, pf.modified = %s, pf.modified_by = %s
where
pfu.user = %s and pf.name = pfu.parent and pf.company = %s
and pfu.default = 1""", (modified, user, user, company), auto_commit=1)
frappe.db.sql(""" update `tabPOS Profile User` pfu, `tabPOS Profile` pf
set
pfu.default = 1, pf.modified = %s, pf.modified_by = %s
where
pfu.user = %s and pf.name = pfu.parent and pf.company = %s and pf.name = %s
""", (modified, user, user, company, pos_profile), auto_commit=1)

View File

@ -12,6 +12,36 @@
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Default",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -54,7 +84,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-10-27 16:46:12.784244",
"modified": "2017-11-23 17:13:16.005475",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile User",

View File

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: POS Profile User", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new POS Profile User
() => frappe.tests.make('POS Profile User', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestPOSProfileUser(unittest.TestCase):
pass

View File

@ -311,6 +311,14 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
},
is_pos: function(frm){
this.set_pos_data();
},
pos_profile: function() {
this.set_pos_data();
},
set_pos_data: function() {
if(this.frm.doc.is_pos) {
if(!this.frm.doc.company) {
this.frm.set_value("is_pos", 0);
@ -323,7 +331,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
callback: function(r) {
if(!r.exc) {
if(r.message && r.message.print_format) {
frm.pos_print_format = r.message.print_format;
me.frm.pos_print_format = r.message.print_format;
}
me.frm.script_manager.trigger("update_stock");
frappe.model.set_default_values(me.frm.doc);
@ -540,6 +548,19 @@ frappe.ui.form.on('Sales Invoice', {
}
};
});
frm.set_query('pos_profile', function(doc) {
if(!doc.company) {
frappe.throw(_('Please set Company'));
}
return {
query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query',
filters: {
company: doc.company
}
};
});
},
//When multiple companies are set up. in case company name is changed set default company address
company:function(frm){

View File

@ -294,21 +294,23 @@ class SalesInvoice(SellingController):
return
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
pos = get_pos_profile(self.company)
if not self.pos_profile:
pos_profile = get_pos_profile(self.company) or {}
self.pos_profile = pos_profile.get('name')
pos = {}
if self.pos_profile:
pos = frappe.get_doc('POS Profile', self.pos_profile)
if not self.get('payments') and not for_validate:
pos_profile = frappe.get_doc('POS Profile', pos.name) if pos else None
update_multi_mode_option(self, pos_profile)
update_multi_mode_option(self, pos)
if not self.account_for_change_amount:
self.account_for_change_amount = frappe.db.get_value('Company', self.company, 'default_cash_account')
if pos:
self.pos_profile = pos.name
if not for_validate and not self.customer:
self.customer = pos.customer
self.mode_of_payment = pos.mode_of_payment
# self.set_customer_defaults()
if pos.get('account_for_change_amount'):
self.account_for_change_amount = pos.get('account_for_change_amount')

View File

@ -449,7 +449,7 @@ erpnext.patches.v8_9.remove_employee_from_salary_structure_parent
erpnext.patches.v8_9.delete_gst_doctypes_for_outside_india_accounts
erpnext.patches.v8_9.set_default_fields_in_variant_settings
erpnext.patches.v8_9.update_billing_gstin_for_indian_account
erpnext.patches.v9_0.add_user_to_child_table_in_pos_profile
erpnext.patches.v9_0.add_user_to_child_table_in_pos_profile #2017-11-28
erpnext.patches.v9_0.set_schedule_date_for_material_request_and_purchase_order
erpnext.patches.v9_0.student_admission_childtable_migrate
erpnext.patches.v9_0.fix_subscription_next_date #2017-10-23

View File

@ -5,23 +5,34 @@ from __future__ import unicode_literals
import frappe
def execute():
doctype = 'POS Profile'
frappe.reload_doc('accounts', 'doctype', doctype)
frappe.reload_doc('accounts', 'doctype', 'pos_profile_user')
frappe.reload_doc('accounts', 'doctype', 'pos_item_group')
frappe.reload_doc('accounts', 'doctype', 'pos_customer_group')
if frappe.db.table_exists("POS Profile User"):
frappe.reload_doc('accounts', 'doctype', 'pos_profile_user')
for doc in frappe.get_all(doctype):
_doc = frappe.get_doc(doctype, doc.name)
user = frappe.db.get_value(doctype, doc.name, 'user')
frappe.db.sql(""" update `tabPOS Profile User`,
(select `tabPOS Profile User`.name from `tabPOS Profile User`, `tabPOS Profile`
where `tabPOS Profile`.name = `tabPOS Profile User`.parent
group by `tabPOS Profile User`.user, `tabPOS Profile`.company) as pfu
set
`tabPOS Profile User`.default = 1
where `tabPOS Profile User`.name = pfu.name""")
else:
doctype = 'POS Profile'
frappe.reload_doc('accounts', 'doctype', doctype)
frappe.reload_doc('accounts', 'doctype', 'pos_profile_user')
frappe.reload_doc('accounts', 'doctype', 'pos_item_group')
frappe.reload_doc('accounts', 'doctype', 'pos_customer_group')
if not user: continue
for doc in frappe.get_all(doctype):
_doc = frappe.get_doc(doctype, doc.name)
user = frappe.db.get_value(doctype, doc.name, 'user')
_doc.append('applicable_for_users', {
'user': user
})
_doc.pos_profile_name = user + ' - ' + _doc.company
_doc.flags.ignore_validate = True
_doc.flags.ignore_mandatory = True
_doc.save()
if not user: continue
_doc.append('applicable_for_users', {
'user': user,
'default': 1
})
_doc.pos_profile_name = user + ' - ' + _doc.company
_doc.flags.ignore_validate = True
_doc.flags.ignore_mandatory = True
_doc.save()

View File

@ -322,7 +322,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
project: item.project || me.frm.doc.project,
qty: item.qty || 1,
stock_qty: item.stock_qty,
conversion_factor: item.conversion_factor
conversion_factor: item.conversion_factor,
pos_profile: me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : ''
}
},
@ -891,7 +892,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
"name": me.frm.doc.name,
"is_return": cint(me.frm.doc.is_return),
"update_stock": in_list(['Sales Invoice', 'Purchase Invoice'], me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
"conversion_factor": me.frm.doc.conversion_factor
"conversion_factor": me.frm.doc.conversion_factor,
"pos_profile": me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : ''
};
},

View File

@ -50,7 +50,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
this.set_online_status();
},
() => this.setup_company(),
() => this.setup_pos_profile(),
() => this.make_new_invoice(),
() => {
frappe.dom.unfreeze();
@ -90,7 +90,6 @@ erpnext.pos.PointOfSale = class PointOfSale {
this.cart = new POSCart({
frm: this.frm,
wrapper: this.wrapper.find('.cart-container'),
pos_profile: this.pos_profile,
events: {
on_customer_change: (customer) => this.frm.set_value('customer', customer),
on_field_change: (item_code, field, value) => {
@ -141,7 +140,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
make_items() {
this.items = new POSItems({
wrapper: this.wrapper.find('.item-container'),
pos_profile: this.pos_profile,
frm: this.frm,
events: {
update_cart: (item, field, value) => {
if(!this.frm.doc.customer) {
@ -292,68 +291,62 @@ erpnext.pos.PointOfSale = class PointOfSale {
})
}
setup_pos_profile() {
change_pos_profile() {
return new Promise((resolve) => {
const load_default = () => {
this.pos_profile = {
company: this.company,
currency: frappe.defaults.get_default('currency'),
selling_price_list: frappe.defaults.get_default('selling_price_list')
};
resolve();
}
const on_submit = ({ pos_profile }) => {
this.get_pos_profile_doc(pos_profile)
.then(doc => {
this.pos_profile = doc;
if (!this.pos_profile) {
load_default();
}
resolve();
});
}
frappe.call({
method: 'erpnext.accounts.doctype.pos_profile.pos_profile.get_pos_profiles_for_user'
}).then((r) => {
if (r && r.message) {
const pos_profiles = r.message.filter(a => a);
if (pos_profiles.length === 0) {
load_default();
} else if(pos_profiles.length === 1) {
// load profile directly
on_submit({pos_profile: pos_profiles[0]});
} else {
// ask prompt
frappe.prompt(
[{ fieldtype: 'Select', label: 'POS Profile', options: pos_profiles }],
on_submit,
__('Select POS Profile')
);
}
} else {
frappe.dom.unfreeze();
frappe.throw(__("POS Profile is required to use Point-of-Sale"));
const on_submit = ({ pos_profile, set_as_default }) => {
if (pos_profile) {
this.frm.doc.pos_profile = pos_profile;
}
});
if (set_as_default) {
frappe.call({
method: "erpnext.accounts.doctype.pos_profile.pos_profile.set_default_profile",
args: {
'pos_profile': pos_profile,
'company': this.frm.doc.company
}
}).then(() => {
this.on_change_pos_profile();
});
} else {
this.on_change_pos_profile();
}
}
frappe.prompt(this.get_promopt_fields(),
on_submit,
__('Select POS Profile')
);
});
}
get_pos_profile_doc(pos_profile_name) {
return new Promise(resolve => {
frappe.call({
method: 'erpnext.accounts.doctype.pos_profile.pos_profile.get_pos_profile',
args: {
pos_profile_name
},
callback: (r) => {
resolve(r.message);
on_change_pos_profile() {
this.set_pos_profile_data()
.then(() => {
this.reset_cart();
if (this.items) {
this.items.reset_items();
}
});
});
}
get_promopt_fields() {
return [{
fieldtype: 'Link',
label: __('POS Profile'),
options: 'POS Profile',
get_query: () => {
return {
query: 'erpnext.accounts.doctype.pos_profile.pos_profile.pos_profile_query',
filters: {
company: this.frm.doc.company
}
};
}
}, {
fieldtype: 'Check',
label: __('Set as default')
}];
}
setup_company() {
@ -378,9 +371,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
.then(() => this.set_pos_profile_data())
.then(() => {
if (this.cart) {
this.cart.frm = this.frm;
this.cart.reset();
this.items.reset_search_field();
this.reset_cart();
} else {
this.make_items();
this.make_cart();
@ -391,29 +382,32 @@ erpnext.pos.PointOfSale = class PointOfSale {
]);
}
reset_cart() {
this.cart.frm = this.frm;
this.cart.reset();
this.items.reset_search_field();
}
make_sales_invoice_frm() {
const doctype = 'Sales Invoice';
return new Promise(resolve => {
if (this.frm) {
this.frm = get_frm(this.pos_profile, this.frm);
this.frm = get_frm(this.frm);
resolve();
} else {
frappe.model.with_doctype(doctype, () => {
this.frm = get_frm(this.pos_profile);
this.frm = get_frm();
resolve();
});
}
});
function get_frm(pos_profile, _frm) {
function get_frm(_frm) {
const page = $('<div>');
const frm = _frm || new _f.Frm(doctype, page, false);
const name = frappe.model.make_new_doc_and_get_name(doctype, true);
frm.refresh(name);
frm.doc.items = [];
if(!frm.doc.company) {
frm.set_value('company', pos_profile.company);
}
frm.doc.is_pos = 1;
return frm;
}
@ -426,6 +420,10 @@ erpnext.pos.PointOfSale = class PointOfSale {
method: "set_missing_values",
}).then((r) => {
if(!r.exc) {
if (!this.frm.doc.pos_profile) {
frappe.dom.unfreeze();
frappe.throw(__("POS Profile is required to use Point-of-Sale"));
}
this.frm.script_manager.trigger("update_stock");
frappe.model.set_default_values(this.frm.doc);
this.frm.cscript.calculate_taxes_and_totals();
@ -433,8 +431,8 @@ erpnext.pos.PointOfSale = class PointOfSale {
}
resolve();
})
})
});
});
}
prepare_menu() {
@ -458,6 +456,10 @@ erpnext.pos.PointOfSale = class PointOfSale {
this.page.add_menu_item(__('POS Settings'), function() {
frappe.set_route('Form', 'POS Settings');
});
this.page.add_menu_item(__('Change POS Profile'), function() {
me.change_pos_profile();
});
}
set_form_action() {
@ -478,12 +480,11 @@ erpnext.pos.PointOfSale = class PointOfSale {
};
class POSCart {
constructor({frm, wrapper, pos_profile, events}) {
constructor({frm, wrapper, events}) {
this.frm = frm;
this.item_data = {};
this.wrapper = wrapper;
this.events = events;
this.pos_profile = pos_profile;
this.make();
this.bind_events();
}
@ -549,7 +550,7 @@ class POSCart {
this.wrapper.find('.grand-total-value').text(
format_currency(this.frm.doc.grand_total, this.frm.currency));
const customer = this.frm.doc.customer || this.pos_profile.customer;
const customer = this.frm.doc.customer;
this.customer_field.set_value(customer);
}
@ -637,7 +638,6 @@ class POSCart {
}
make_customer_field() {
let customer = this.frm.doc.customer || this.pos_profile['customer'];
this.customer_field = frappe.ui.form.make_control({
df: {
fieldtype: 'Link',
@ -653,9 +653,7 @@ class POSCart {
render_input: true
});
if (customer) {
this.customer_field.set_value(customer);
}
this.customer_field.set_value(this.frm.doc.customer);
}
make_numpad() {
@ -920,19 +918,22 @@ class POSCart {
}
class POSItems {
constructor({wrapper, pos_profile, events}) {
constructor({wrapper, frm, events}) {
this.wrapper = wrapper;
this.pos_profile = pos_profile;
this.frm = frm;
this.items = {};
this.events = events;
this.currency = this.pos_profile.currency;
this.currency = this.frm.doc.currency;
this.make_dom();
this.make_fields();
this.init_clusterize();
this.bind_events();
this.load_items_data();
}
load_items_data() {
// bootstrap with 20 items
this.get_items()
.then(({ items }) => {
@ -942,6 +943,12 @@ class POSItems {
});
}
reset_items() {
this.wrapper.find('.pos-items').empty();
this.init_clusterize();
this.load_items_data();
}
make_dom() {
this.wrapper.html(`
<div class="fields">
@ -1165,7 +1172,7 @@ class POSItems {
args: {
start,
page_length,
'price_list': this.pos_profile.selling_price_list,
'price_list': this.frm.doc.selling_price_list,
item_group,
search_value
}

View File

@ -394,7 +394,7 @@ def get_pos_profile_item_details(company, args, pos_profile=None):
res = frappe._dict()
if not pos_profile:
pos_profile = get_pos_profile(company)
pos_profile = get_pos_profile(company, args.get('pos_profile'))
if pos_profile:
for fieldname in ("income_account", "cost_center", "warehouse", "expense_account"):
@ -408,17 +408,32 @@ def get_pos_profile_item_details(company, args, pos_profile=None):
return res
@frappe.whitelist()
def get_pos_profile(company):
pos_profile = frappe.db.sql("""select * from `tabPOS Profile` where user = %s
and company = %s and ifnull(disabled,0) != 1""", (frappe.session['user'], company), as_dict=1)
def get_pos_profile(company, pos_profile=None, user=None):
if pos_profile:
return frappe.get_doc('POS Profile', pos_profile)
if not user:
user = frappe.session['user']
pos_profile = frappe.db.sql("""select pf.*
from
`tabPOS Profile` pf, `tabPOS Profile User` pfu
where
pfu.parent = pf.name and pfu.user = %s and pf.company = %s
and pf.disabled = 0 and pfu.default=1""", (user, company), as_dict=1)
if not pos_profile:
pos_profile = frappe.db.sql("""select * from `tabPOS Profile`
where ifnull(user,'') = '' and company = %s and ifnull(disabled,0) != 1""", company, as_dict=1)
pos_profile = frappe.db.sql("""select pf.*
from
`tabPOS Profile` pf left join `tabPOS Profile User` pfu
on
pf.name = pfu.parent
where
ifnull(pfu.user, '') = '' and pf.company = %s
and pf.disabled = 0""", (company), as_dict=1)
return pos_profile and pos_profile[0] or None
def get_serial_nos_by_fifo(args):
if frappe.db.get_single_value("Stock Settings", "automatically_set_serial_nos_based_on_fifo"):
return "\n".join(frappe.db.sql_list("""select name from `tabSerial No`