fix: multiple pos issues (#23297)

* fix: returns can be made against unconsolidated invoices

* fix: indentation

* fix: mode of payment not fetching for pos returns

* patch: default pos profile print format

* fix: tests

* chore: clean up retail desk page
This commit is contained in:
Saqib 2020-09-10 19:28:46 +05:30 committed by GitHub
parent ec6a97fb6a
commit cd89994b33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 521 additions and 495 deletions

View File

@ -43,7 +43,7 @@
{ {
"hidden": 0, "hidden": 0,
"label": "Bank Statement", "label": "Bank Statement",
"links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]" "links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]"
}, },
{ {
"hidden": 0, "hidden": 0,
@ -98,7 +98,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Accounting", "label": "Accounting",
"modified": "2020-09-03 10:37:07.865801", "modified": "2020-09-09 11:45:33.766400",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting", "name": "Accounting",
@ -147,11 +147,6 @@
"link_to": "Trial Balance", "link_to": "Trial Balance",
"type": "Report" "type": "Report"
}, },
{
"label": "Point of Sale",
"link_to": "point-of-sale",
"type": "Page"
},
{ {
"label": "Dashboard", "label": "Dashboard",
"link_to": "Accounts", "link_to": "Accounts",

View File

@ -55,14 +55,48 @@ frappe.ui.form.on('POS Closing Entry', {
}, },
callback: (r) => { callback: (r) => {
let pos_docs = r.message; let pos_docs = r.message;
set_form_data(pos_docs, frm) set_form_data(pos_docs, frm);
refresh_fields(frm) refresh_fields(frm);
set_html_data(frm) set_html_data(frm);
} }
}) })
} }
}); });
cur_frm.cscript.before_pos_transactions_remove = function(doc, cdt, cdn) {
const removed_row = locals[cdt][cdn];
if (!removed_row.pos_invoice) return;
frappe.db.get_doc("POS Invoice", removed_row.pos_invoice).then(doc => {
cur_frm.doc.grand_total -= flt(doc.grand_total);
cur_frm.doc.net_total -= flt(doc.net_total);
cur_frm.doc.total_quantity -= flt(doc.total_qty);
refresh_payments(doc, cur_frm, 1);
refresh_taxes(doc, cur_frm, 1);
refresh_fields(cur_frm);
set_html_data(cur_frm);
});
}
frappe.ui.form.on('POS Invoice Reference', {
pos_invoice(frm, cdt, cdn) {
const added_row = locals[cdt][cdn];
if (!added_row.pos_invoice) return;
frappe.db.get_doc("POS Invoice", added_row.pos_invoice).then(doc => {
frm.doc.grand_total += flt(doc.grand_total);
frm.doc.net_total += flt(doc.net_total);
frm.doc.total_quantity += flt(doc.total_qty);
refresh_payments(doc, frm);
refresh_taxes(doc, frm);
refresh_fields(frm);
set_html_data(frm);
});
}
})
frappe.ui.form.on('POS Closing Entry Detail', { frappe.ui.form.on('POS Closing Entry Detail', {
closing_amount: (frm, cdt, cdn) => { closing_amount: (frm, cdt, cdn) => {
const row = locals[cdt][cdn]; const row = locals[cdt][cdn];
@ -76,8 +110,8 @@ function set_form_data(data, frm) {
frm.doc.grand_total += flt(d.grand_total); frm.doc.grand_total += flt(d.grand_total);
frm.doc.net_total += flt(d.net_total); frm.doc.net_total += flt(d.net_total);
frm.doc.total_quantity += flt(d.total_qty); frm.doc.total_quantity += flt(d.total_qty);
add_to_payments(d, frm); refresh_payments(d, frm);
add_to_taxes(d, frm); refresh_taxes(d, frm);
}); });
} }
@ -90,11 +124,12 @@ function add_to_pos_transaction(d, frm) {
}) })
} }
function add_to_payments(d, frm) { function refresh_payments(d, frm, remove) {
d.payments.forEach(p => { d.payments.forEach(p => {
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment); const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment);
if (payment) { if (payment) {
payment.expected_amount += flt(p.amount); if (!remove) payment.expected_amount += flt(p.amount);
else payment.expected_amount -= flt(p.amount);
} else { } else {
frm.add_child("payment_reconciliation", { frm.add_child("payment_reconciliation", {
mode_of_payment: p.mode_of_payment, mode_of_payment: p.mode_of_payment,
@ -105,11 +140,12 @@ function add_to_payments(d, frm) {
}) })
} }
function add_to_taxes(d, frm) { function refresh_taxes(d, frm, remove) {
d.taxes.forEach(t => { d.taxes.forEach(t => {
const tax = frm.doc.taxes.find(tx => tx.account_head === t.account_head && tx.rate === t.rate); const tax = frm.doc.taxes.find(tx => tx.account_head === t.account_head && tx.rate === t.rate);
if (tax) { if (tax) {
tax.amount += flt(t.tax_amount); if (!remove) tax.amount += flt(t.tax_amount);
else tax.amount -= flt(t.tax_amount);
} else { } else {
frm.add_child("taxes", { frm.add_child("taxes", {
account_head: t.account_head, account_head: t.account_head,

View File

@ -279,7 +279,8 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Return (Credit Note)", "label": "Is Return (Credit Note)",
"no_copy": 1, "no_copy": 1,
"print_hide": 1 "print_hide": 1,
"set_only_once": 1
}, },
{ {
"fieldname": "column_break1", "fieldname": "column_break1",
@ -1578,9 +1579,10 @@
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-29 15:08:39.337385", "modified": "2020-09-07 12:43:09.138720",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Invoice", "name": "POS Invoice",

View File

@ -182,8 +182,9 @@ class TestPOSInvoice(unittest.TestCase):
def test_pos_returns_with_repayment(self): def test_pos_returns_with_repayment(self):
pos = create_pos_invoice(qty = 10, do_not_save=True) pos = create_pos_invoice(qty = 10, do_not_save=True)
pos.set('payments', [])
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500}) pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500})
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500}) pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500, 'default': 1})
pos.insert() pos.insert()
pos.submit() pos.submit()
@ -200,8 +201,9 @@ class TestPOSInvoice(unittest.TestCase):
income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105, income_account = "Sales - _TC", expense_account = "Cost of Goods Sold - _TC", rate=105,
cost_center = "Main - _TC", do_not_save=True) cost_center = "Main - _TC", do_not_save=True)
pos.set('payments', [])
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 50}) pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 50})
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60}) pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60, 'default': 1})
pos.insert() pos.insert()
pos.submit() pos.submit()

View File

@ -24,11 +24,20 @@ class POSInvoiceMergeLog(Document):
def validate_pos_invoice_status(self): def validate_pos_invoice_status(self):
for d in self.pos_invoices: for d in self.pos_invoices:
status, docstatus = frappe.db.get_value('POS Invoice', d.pos_invoice, ['status', 'docstatus']) status, docstatus, is_return, return_against = frappe.db.get_value(
'POS Invoice', d.pos_invoice, ['status', 'docstatus', 'is_return', 'return_against'])
if docstatus != 1: if docstatus != 1:
frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, d.pos_invoice)) frappe.throw(_("Row #{}: POS Invoice {} is not submitted yet").format(d.idx, d.pos_invoice))
if status in ['Consolidated']: if status == "Consolidated":
frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, d.pos_invoice, status)) frappe.throw(_("Row #{}: POS Invoice {} has been {}").format(d.idx, d.pos_invoice, status))
if is_return and return_against not in [d.pos_invoice for d in self.pos_invoices] and status != "Consolidated":
# if return entry is not getting merged in the current pos closing and if it is not consolidated
frappe.throw(
_("Row #{}: Return Invoice {} cannot be made against unconsolidated invoice. \
You can add original invoice {} manually to proceed.")
.format(d.idx, frappe.bold(d.pos_invoice), frappe.bold(return_against))
)
def on_submit(self): def on_submit(self):
pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices] pos_invoice_docs = [frappe.get_doc("POS Invoice", d.pos_invoice) for d in self.pos_invoices]
@ -36,12 +45,12 @@ class POSInvoiceMergeLog(Document):
returns = [d for d in pos_invoice_docs if d.get('is_return') == 1] returns = [d for d in pos_invoice_docs if d.get('is_return') == 1]
sales = [d for d in pos_invoice_docs if d.get('is_return') == 0] sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
sales_invoice, credit_note = "", ""
if sales:
sales_invoice = self.process_merging_into_sales_invoice(sales) sales_invoice = self.process_merging_into_sales_invoice(sales)
if len(returns): if returns:
credit_note = self.process_merging_into_credit_note(returns) credit_note = self.process_merging_into_credit_note(returns)
else:
credit_note = ""
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log

View File

@ -242,7 +242,8 @@ def make_return_doc(doctype, source_name, target_doc=None):
'type': data.type, 'type': data.type,
'amount': -1 * paid_amount, 'amount': -1 * paid_amount,
'base_amount': -1 * base_paid_amount, 'base_amount': -1 * base_paid_amount,
'account': data.account 'account': data.account,
'default': data.default
}) })
if doc.is_pos: if doc.is_pos:
doc.paid_amount = -1 * source.paid_amount doc.paid_amount = -1 * source.paid_amount

View File

@ -725,3 +725,4 @@ erpnext.patches.v12_0.rename_lost_reason_detail
erpnext.patches.v13_0.drop_razorpay_payload_column erpnext.patches.v13_0.drop_razorpay_payload_column
erpnext.patches.v13_0.update_start_end_date_for_old_shift_assignment erpnext.patches.v13_0.update_start_end_date_for_old_shift_assignment
erpnext.patches.v13_0.setting_custom_roles_for_some_regional_reports erpnext.patches.v13_0.setting_custom_roles_for_some_regional_reports
erpnext.patches.v13_0.change_default_pos_print_format

View File

@ -0,0 +1,8 @@
from __future__ import unicode_literals
import frappe
def execute():
frappe.db.sql(
"""UPDATE `tabPOS Profile` profile
SET profile.`print_format` = 'POS Invoice'
WHERE profile.`print_format` = 'Point of Sale'""")

View File

@ -673,23 +673,14 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
); );
} }
frappe.db.get_value('Sales Invoice Payment', {'parent': this.frm.doc.pos_profile, 'default': 1}, this.frm.doc.payments.find(pay => {
['mode_of_payment', 'account', 'type'], (value) => { if (pay.default) {
if (this.frm.is_dirty()) { pay.amount = total_amount_to_pay;
frappe.model.clear_table(this.frm.doc, 'payments');
if (value) {
let row = frappe.model.add_child(this.frm.doc, 'Sales Invoice Payment', 'payments');
row.mode_of_payment = value.mode_of_payment;
row.type = value.type;
row.account = value.account;
row.default = 1;
row.amount = total_amount_to_pay;
} else { } else {
this.frm.set_value('is_pos', 1); pay.amount = 0.0
} }
});
this.frm.refresh_fields(); this.frm.refresh_fields();
}
}, 'Sales Invoice');
this.calculate_paid_amount(); this.calculate_paid_amount();
}, },

View File

@ -2,8 +2,18 @@
"cards": [ "cards": [
{ {
"hidden": 0, "hidden": 0,
"label": "Retail Operations", "label": "Settings & Configurations",
"links": "[\n {\n \"description\": \"Setup default values for POS Invoices\",\n \"label\": \"Point of Sale Profile\",\n \"name\": \"POS Profile\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"POS Profile\"\n ],\n \"description\": \"Point of Sale\",\n \"label\": \"Point of Sale\",\n \"name\": \"point-of-sale\",\n \"onboard\": 1,\n \"type\": \"page\"\n },\n {\n \"description\": \"Setup mode of POS (Online / Offline)\",\n \"label\": \"POS Settings\",\n \"name\": \"POS Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Cashier Closing\",\n \"label\": \"Cashier Closing\",\n \"name\": \"Cashier Closing\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"To make Customer based incentive schemes.\",\n \"label\": \"Loyalty Program\",\n \"name\": \"Loyalty Program\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"To view logs of Loyalty Points assigned to a Customer.\",\n \"label\": \"Loyalty Point Entry\",\n \"name\": \"Loyalty Point Entry\",\n \"type\": \"doctype\"\n }\n]" "links": "[\n {\n \"description\": \"Setup default values for POS Invoices\",\n \"label\": \"Point-of-Sale Profile\",\n \"name\": \"POS Profile\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"POS Settings\",\n \"name\": \"POS Settings\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Loyalty Program",
"links": "[\n {\n \"description\": \"To make Customer based incentive schemes.\",\n \"label\": \"Loyalty Program\",\n \"name\": \"Loyalty Program\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"To view logs of Loyalty Points assigned to a Customer.\",\n \"label\": \"Loyalty Point Entry\",\n \"name\": \"Loyalty Point Entry\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Opening & Closing",
"links": "[\n {\n \"label\": \"POS Opening Entry\",\n \"name\": \"POS Opening Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"POS Closing Entry\",\n \"name\": \"POS Closing Entry\",\n \"type\": \"doctype\"\n }\n]"
} }
], ],
"category": "Domains", "category": "Domains",
@ -18,7 +28,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Retail", "label": "Retail",
"modified": "2020-08-20 18:00:07.515691", "modified": "2020-09-09 11:46:28.297435",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Retail", "name": "Retail",
@ -28,25 +38,10 @@
"restrict_to_domain": "Retail", "restrict_to_domain": "Retail",
"shortcuts": [ "shortcuts": [
{ {
"color": "#9deca2",
"doc_view": "", "doc_view": "",
"format": "{} Active", "label": "Point Of Sale",
"label": "Point of Sale Profile",
"link_to": "POS Profile",
"stats_filter": "{\n \"disabled\": 0\n}",
"type": "DocType"
},
{
"doc_view": "",
"label": "Point of Sale",
"link_to": "point-of-sale", "link_to": "point-of-sale",
"type": "Page" "type": "Page"
},
{
"doc_view": "",
"label": "POS Settings",
"link_to": "POS Settings",
"type": "DocType"
} }
] ]
} }

View File

@ -9,7 +9,7 @@ frappe.pages['point-of-sale'].on_page_load = function(wrapper) {
title: __('Point of Sale'), title: __('Point of Sale'),
single_column: true single_column: true
}); });
// online
wrapper.pos = new erpnext.PointOfSale.Controller(wrapper); wrapper.pos = new erpnext.PointOfSale.Controller(wrapper);
window.cur_pos = wrapper.pos; window.cur_pos = wrapper.pos;
}; };

View File

@ -36,7 +36,7 @@ erpnext.PointOfSale.Controller = class {
const table_fields = [ const table_fields = [
{ fieldname: "mode_of_payment", fieldtype: "Link", in_list_view: 1, label: "Mode of Payment", options: "Mode of Payment", reqd: 1 }, { fieldname: "mode_of_payment", fieldtype: "Link", in_list_view: 1, label: "Mode of Payment", options: "Mode of Payment", reqd: 1 },
{ fieldname: "opening_amount", fieldtype: "Currency", default: 0, in_list_view: 1, label: "Opening Amount", { fieldname: "opening_amount", fieldtype: "Currency", default: 0, in_list_view: 1, label: "Opening Amount",
options: "company:company_currency", reqd: 1 } options: "company:company_currency" }
]; ];
const dialog = new frappe.ui.Dialog({ const dialog = new frappe.ui.Dialog({
@ -51,30 +51,17 @@ erpnext.PointOfSale.Controller = class {
options: 'POS Profile', fieldname: 'pos_profile', reqd: 1, options: 'POS Profile', fieldname: 'pos_profile', reqd: 1,
onchange: () => { onchange: () => {
const pos_profile = dialog.fields_dict.pos_profile.get_value(); const pos_profile = dialog.fields_dict.pos_profile.get_value();
const company = dialog.fields_dict.company.get_value();
const user = frappe.session.user
if (!pos_profile || !company || !user) return; if (!pos_profile) return;
// auto fetch last closing entry's balance details frappe.db.get_doc("POS Profile", pos_profile).then(doc => {
frappe.db.get_list("POS Closing Entry", {
filters: { company, pos_profile, user },
limit: 1,
order_by: 'period_end_date desc'
}).then((res) => {
if (!res.length) return;
const pos_closing_entry = res[0];
frappe.db.get_doc("POS Closing Entry", pos_closing_entry.name).then(({ payment_reconciliation }) => {
dialog.fields_dict.balance_details.df.data = []; dialog.fields_dict.balance_details.df.data = [];
payment_reconciliation.forEach(pay => { doc.payments.forEach(pay => {
const { mode_of_payment } = pay; const { mode_of_payment } = pay;
dialog.fields_dict.balance_details.df.data.push({ dialog.fields_dict.balance_details.df.data.push({ mode_of_payment });
mode_of_payment: mode_of_payment
});
}); });
dialog.fields_dict.balance_details.grid.refresh(); dialog.fields_dict.balance_details.grid.refresh();
}); });
});
} }
}, },
{ {

View File

@ -23,8 +23,7 @@ erpnext.PointOfSale.NumberPad = class {
return a + row.reduce((a2, number, j) => { return a + row.reduce((a2, number, j) => {
const class_to_append = css_classes && css_classes[i] ? css_classes[i][j] : ''; const class_to_append = css_classes && css_classes[i] ? css_classes[i][j] : '';
const fieldname = fieldnames && fieldnames[number] ? const fieldname = fieldnames && fieldnames[number] ?
fieldnames[number] : fieldnames[number] : typeof number === 'string' ? frappe.scrub(number) : number;
typeof number === 'string' ? frappe.scrub(number) : number;
return a2 + `<div class="numpad-btn pointer no-select rounded ${class_to_append} return a2 + `<div class="numpad-btn pointer no-select rounded ${class_to_append}
flex items-center justify-center h-16 text-md border-grey border" data-button-value="${fieldname}">${number}</div>` flex items-center justify-center h-16 text-md border-grey border" data-button-value="${fieldname}">${number}</div>`
@ -44,6 +43,6 @@ erpnext.PointOfSale.NumberPad = class {
this.wrapper.on('click', '.numpad-btn', function() { this.wrapper.on('click', '.numpad-btn', function() {
const $btn = $(this); const $btn = $(this);
me.events.numpad_event($btn); me.events.numpad_event($btn);
}) });
} }
} }