refactor: sales invoice integration with pos (#47713)

* fix: invoice doctype selection in accounts settings

* test: change in accounts settings on sales invoice

* test: refactored pos_invoice_merge_log tests

* test: pos closing entry and pos invoice

* fix: closing voucher details style

* refactor: renamed fields and removed repeated methods

* fix: patch to rename pos closing entry fields

* refactor: replaced get_doc with sql query

* fix: restrict cancelling sales invoice on cancellation of pos closing entry

* fix: removed payment reconciliation summary field and rearranged total section fields

* refactor: set_posting_date_and_time

* test: create_sales_invoice added args for is_created_using_pos

* test: added test for sales invoice creation during pos invoice mode

* test: added test for pos invoice creation during sales invoice mode

* fix: moved invoice type selection in pos settings

* fix: pos additional fields label

* refactor: pos closing entry

rearranged fields, removed rate field from taxes field, fetching payments and taxes details

* test: moved invoice creation in functions

* refactor: using as_dict=1

* fix: wrong table chosen in query

* fix: variable rename

* test: fixed failing tests

* test: fixed pos_closing_entry tests
This commit is contained in:
Diptanil Saha
2025-06-10 17:51:11 +05:30
committed by GitHub
parent 6529b288c2
commit 4e537cdb74
20 changed files with 891 additions and 817 deletions

View File

@@ -65,7 +65,6 @@
"pos_setting_section", "pos_setting_section",
"post_change_gl_entries", "post_change_gl_entries",
"column_break_xrnd", "column_break_xrnd",
"use_sales_invoice_in_pos",
"assets_tab", "assets_tab",
"asset_settings_section", "asset_settings_section",
"calculate_depr_using_total_days", "calculate_depr_using_total_days",
@@ -550,13 +549,6 @@
"fieldname": "column_break_xrnd", "fieldname": "column_break_xrnd",
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{
"default": "0",
"description": "If enabled, Sales Invoice will be generated instead of POS Invoice in POS Transactions for real-time update of G/L and Stock Ledger.",
"fieldname": "use_sales_invoice_in_pos",
"fieldtype": "Check",
"label": "Use Sales Invoice"
},
{ {
"default": "Buffered Cursor", "default": "Buffered Cursor",
"fieldname": "receivable_payable_fetch_method", "fieldname": "receivable_payable_fetch_method",
@@ -630,7 +622,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2025-05-27 17:52:03.460522", "modified": "2025-06-06 11:03:28.095723",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",

View File

@@ -72,7 +72,6 @@ class AccountsSettings(Document):
unlink_advance_payment_on_cancelation_of_order: DF.Check unlink_advance_payment_on_cancelation_of_order: DF.Check
unlink_payment_on_cancellation_of_invoice: DF.Check unlink_payment_on_cancellation_of_invoice: DF.Check
use_new_budget_controller: DF.Check use_new_budget_controller: DF.Check
use_sales_invoice_in_pos: DF.Check
# end: auto-generated types # end: auto-generated types
def validate(self): def validate(self):
@@ -99,9 +98,6 @@ class AccountsSettings(Document):
if old_doc.acc_frozen_upto != self.acc_frozen_upto: if old_doc.acc_frozen_upto != self.acc_frozen_upto:
self.validate_pending_reposts() self.validate_pending_reposts()
if old_doc.use_sales_invoice_in_pos != self.use_sales_invoice_in_pos:
self.validate_invoice_mode_switch_in_pos()
if clear_cache: if clear_cache:
frappe.clear_cache() frappe.clear_cache()
@@ -145,15 +141,3 @@ class AccountsSettings(Document):
if self.has_value_changed("reconciliation_queue_size"): if self.has_value_changed("reconciliation_queue_size"):
if cint(self.reconciliation_queue_size) < 5 or cint(self.reconciliation_queue_size) > 100: if cint(self.reconciliation_queue_size) < 5 or cint(self.reconciliation_queue_size) > 100:
frappe.throw(_("Queue Size should be between 5 and 100")) frappe.throw(_("Queue Size should be between 5 and 100"))
def validate_invoice_mode_switch_in_pos(self):
pos_opening_entries_count = frappe.db.count(
"POS Opening Entry", filters={"docstatus": 1, "status": "Open"}
)
if pos_opening_entries_count:
frappe.throw(
_("{0} can be enabled/disabled after all the POS Opening Entries are closed.").format(
frappe.bold(_("Use Sales Invoice"))
),
title=_("Switch Invoice Mode Error"),
)

View File

@@ -1,11 +1,11 @@
<div class="clearfix"></div> <div class="clearfix"></div>
<div class="box"> <div class="box">
<div class="grid-body"> <div class="grid-body" style="background-color: transparent;">
<div class="rows text-center"> <div class="rows text-center">
<!-- Sales summary section --> <!-- Sales summary section -->
<div> <div>
<h6 class="text-center uppercase" style="color: #8D99A6">{{ _("Sales Summary") }}</h6> <h6 class="text-center uppercase">{{ _("Sales Summary") }}</h6>
<div class="tax-break-up" style="overflow-x: auto;"> <div class="tax-break-up" style="overflow-x: auto;">
<table class="table table-bordered table-hover"> <table class="table table-bordered table-hover">
<thead> <thead>
@@ -32,7 +32,7 @@
<!-- Mode of payment section --> <!-- Mode of payment section -->
<div> <div>
<h6 class="text-center uppercase" style="color: #8D99A6">{{ _("Mode of Payments") }}</h6> <h6 class="text-center uppercase">{{ _("Mode of Payments") }}</h6>
<div class="tax-break-up" style="overflow-x: auto;"> <div class="tax-break-up" style="overflow-x: auto;">
<table class="table table-bordered table-hover"> <table class="table table-bordered table-hover">
<thead> <thead>
@@ -57,7 +57,7 @@
<!-- Taxes section --> <!-- Taxes section -->
{% if data.taxes %} {% if data.taxes %}
<div> <div>
<h6 class="text-center uppercase" style="color: #8D99A6">{{ _("Taxes") }}</h6> <h6 class="text-center uppercase">{{ _("Taxes") }}</h6>
<div class="tax-break-up" style="overflow-x: auto;"> <div class="tax-break-up" style="overflow-x: auto;">
<table class="table table-bordered table-hover"> <table class="table table-bordered table-hover">
<thead> <thead>

View File

@@ -3,7 +3,7 @@
frappe.ui.form.on("POS Closing Entry", { frappe.ui.form.on("POS Closing Entry", {
onload: async function (frm) { onload: async function (frm) {
frm.ignore_doctypes_on_cancel_all = ["POS Invoice Merge Log"]; frm.ignore_doctypes_on_cancel_all = ["POS Invoice Merge Log", "Sales Invoice"];
frm.set_query("pos_profile", function (doc) { frm.set_query("pos_profile", function (doc) {
return { return {
filters: { user: doc.user }, filters: { user: doc.user },
@@ -36,17 +36,6 @@ frappe.ui.form.on("POS Closing Entry", {
} }
}); });
const is_pos_using_sales_invoice = await frappe.db.get_single_value(
"Accounts Settings",
"use_sales_invoice_in_pos"
);
if (is_pos_using_sales_invoice) {
frm.set_df_property("pos_transactions", "hidden", 1);
}
set_html_data(frm);
if (frm.doc.docstatus == 1) { if (frm.doc.docstatus == 1) {
if (!frm.doc.posting_date) { if (!frm.doc.posting_date) {
frm.set_value("posting_date", frappe.datetime.nowdate()); frm.set_value("posting_date", frappe.datetime.nowdate());
@@ -91,8 +80,7 @@ frappe.ui.form.on("POS Closing Entry", {
frappe.run_serially([ frappe.run_serially([
() => frappe.dom.freeze(__("Loading Invoices! Please Wait...")), () => frappe.dom.freeze(__("Loading Invoices! Please Wait...")),
() => frm.trigger("set_opening_amounts"), () => frm.trigger("set_opening_amounts"),
() => frm.trigger("get_pos_invoices"), () => frm.trigger("get_invoices"),
() => frm.trigger("get_sales_invoices"),
() => frappe.dom.unfreeze(), () => frappe.dom.unfreeze(),
]); ]);
} }
@@ -112,9 +100,9 @@ frappe.ui.form.on("POS Closing Entry", {
}); });
}, },
get_pos_invoices(frm) { get_invoices(frm) {
return frappe.call({ return frappe.call({
method: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_pos_invoices", method: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_invoices",
args: { args: {
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date), start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date), end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
@@ -122,101 +110,14 @@ frappe.ui.form.on("POS Closing Entry", {
user: frm.doc.user, user: frm.doc.user,
}, },
callback: (r) => { callback: (r) => {
let pos_docs = r.message; let inv_docs = r.message.invoices;
set_pos_transaction_form_data(pos_docs, frm); set_transaction_form_data(inv_docs, frm);
refresh_payments(r.message.payments, frm);
add_taxes(r.message.taxes, frm);
refresh_fields(frm); refresh_fields(frm);
set_html_data(frm);
}, },
}); });
}, },
get_sales_invoices(frm) {
return frappe.call({
method: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_sales_invoices",
args: {
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
pos_profile: frm.doc.pos_profile,
user: frm.doc.user,
},
callback: (r) => {
let sales_docs = r.message;
set_sales_invoice_transaction_form_data(sales_docs, frm);
refresh_fields(frm);
set_html_data(frm);
},
});
},
before_save: async function (frm) {
frappe.dom.freeze(__("Processing Sales! Please Wait..."));
frm.set_value("grand_total", 0);
frm.set_value("net_total", 0);
frm.set_value("total_quantity", 0);
frm.set_value("taxes", []);
for (let row of frm.doc.payment_reconciliation) {
row.expected_amount = row.opening_amount;
}
const is_pos_using_sales_invoice = await frappe.db.get_single_value(
"Accounts Settings",
"use_sales_invoice_in_pos"
);
if (is_pos_using_sales_invoice) {
await Promise.all([
frappe.call({
method: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_pos_invoices",
args: {
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
pos_profile: frm.doc.pos_profile,
user: frm.doc.user,
},
callback: (r) => {
let pos_invoices = r.message;
for (let doc of pos_invoices) {
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, false);
refresh_taxes(doc, frm);
refresh_fields(frm);
set_html_data(frm);
}
},
}),
]);
}
await Promise.all([
frappe.call({
method: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_sales_invoices",
args: {
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
pos_profile: frm.doc.pos_profile,
user: frm.doc.user,
},
callback: (r) => {
let sales_invoices = r.message;
for (let doc of sales_invoices) {
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, false);
refresh_taxes(doc, frm);
refresh_fields(frm);
set_html_data(frm);
}
},
}),
]);
frappe.dom.unfreeze();
},
}); });
frappe.ui.form.on("POS Closing Entry Detail", { frappe.ui.form.on("POS Closing Entry Detail", {
@@ -226,57 +127,35 @@ frappe.ui.form.on("POS Closing Entry Detail", {
}, },
}); });
function set_pos_transaction_form_data(data, frm) { function set_transaction_form_data(data, frm) {
data.forEach((d) => { data.forEach((d) => {
add_to_pos_transaction(d, frm); add_to_transaction(d, 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);
refresh_payments(d, frm, true); frm.doc.total_taxes_and_charges += flt(d.total_taxes_and_charges);
refresh_taxes(d, frm);
}); });
} }
function set_sales_invoice_transaction_form_data(data, frm) { function add_to_transaction(d, frm) {
data.forEach((d) => { const field = d.doctype === "POS Invoice" ? "pos_invoices" : "sales_invoices";
add_to_sales_invoice_transaction(d, frm); frm.add_child(field, {
frm.doc.grand_total += flt(d.grand_total);
frm.doc.net_total += flt(d.net_total);
frm.doc.total_quantity += flt(d.total_qty);
refresh_payments(d, frm, true);
refresh_taxes(d, frm);
});
}
function add_to_pos_transaction(d, frm) {
frm.add_child("pos_transactions", {
pos_invoice: d.name,
posting_date: d.posting_date, posting_date: d.posting_date,
grand_total: d.grand_total, grand_total: d.grand_total,
customer: d.customer, customer: d.customer,
...(d.doctype === "POS Invoice" && { pos_invoice: d.name }),
...(d.doctype === "Sales Invoice" && { sales_invoice: d.name }),
}); });
} }
function add_to_sales_invoice_transaction(d, frm) { function refresh_payments(payments, frm) {
frm.add_child("sales_invoice_transactions", { payments.forEach((p) => {
sales_invoice: d.name,
posting_date: d.posting_date,
grand_total: d.grand_total,
customer: d.customer,
});
}
function refresh_payments(d, frm, is_new) {
d.payments.forEach((p) => {
const payment = frm.doc.payment_reconciliation.find( const payment = frm.doc.payment_reconciliation.find(
(pay) => pay.mode_of_payment === p.mode_of_payment (pay) => pay.mode_of_payment === p.mode_of_payment
); );
if (p.account == d.account_for_change_amount) {
p.amount -= flt(d.change_amount);
}
if (payment) { if (payment) {
payment.expected_amount += flt(p.amount); payment.expected_amount += flt(p.amount);
if (is_new) payment.closing_amount = payment.expected_amount; payment.closing_amount = payment.expected_amount;
payment.difference = payment.closing_amount - payment.expected_amount; payment.difference = payment.closing_amount - payment.expected_amount;
} else { } else {
frm.add_child("payment_reconciliation", { frm.add_child("payment_reconciliation", {
@@ -289,49 +168,33 @@ function refresh_payments(d, frm, is_new) {
}); });
} }
function refresh_taxes(d, frm) { function add_taxes(taxes, frm) {
d.taxes.forEach((t) => { taxes.forEach((t) => {
const tax = frm.doc.taxes.find((tx) => tx.account_head === t.account_head && tx.rate === t.rate); frm.add_child("taxes", {
if (tax) { account_head: t.account_head,
tax.amount += flt(t.tax_amount); amount: t.tax_amount,
} else { });
frm.add_child("taxes", {
account_head: t.account_head,
rate: t.rate,
amount: t.tax_amount,
});
}
}); });
} }
function reset_values(frm) { function reset_values(frm) {
frm.set_value("pos_transactions", []); frm.set_value("pos_invoices", []);
frm.set_value("sales_invoice_transactions", []); frm.set_value("sales_invoices", []);
frm.set_value("payment_reconciliation", []); frm.set_value("payment_reconciliation", []);
frm.set_value("taxes", []); frm.set_value("taxes", []);
frm.set_value("grand_total", 0); frm.set_value("grand_total", 0);
frm.set_value("net_total", 0); frm.set_value("net_total", 0);
frm.set_value("total_taxes_and_charges", 0);
frm.set_value("total_quantity", 0); frm.set_value("total_quantity", 0);
} }
function refresh_fields(frm) { function refresh_fields(frm) {
frm.refresh_field("pos_transactions"); frm.refresh_field("pos_invoices");
frm.refresh_field("sales_invoice_transactions"); frm.refresh_field("sales_invoices");
frm.refresh_field("payment_reconciliation"); frm.refresh_field("payment_reconciliation");
frm.refresh_field("taxes"); frm.refresh_field("taxes");
frm.refresh_field("grand_total"); frm.refresh_field("grand_total");
frm.refresh_field("net_total"); frm.refresh_field("net_total");
frm.refresh_field("total_taxes_and_charges");
frm.refresh_field("total_quantity"); frm.refresh_field("total_quantity");
} }
function set_html_data(frm) {
if (frm.doc.docstatus === 1 && frm.doc.status == "Submitted") {
frappe.call({
method: "get_payment_reconciliation_details",
doc: frm.doc,
callback: (r) => {
frm.get_field("payment_reconciliation_details").$wrapper.html(r.message);
},
});
}
}

View File

@@ -20,18 +20,19 @@
"pos_profile", "pos_profile",
"user", "user",
"section_break_12", "section_break_12",
"pos_transactions", "pos_invoices",
"sales_invoice_transactions", "sales_invoices",
"section_break_9", "taxes_and_charges_section",
"payment_reconciliation_details", "taxes",
"section_break_13",
"column_break_16",
"total_quantity",
"column_break_ywgl",
"net_total",
"total_taxes_and_charges",
"grand_total",
"section_break_11", "section_break_11",
"payment_reconciliation", "payment_reconciliation",
"section_break_13",
"grand_total",
"net_total",
"total_quantity",
"column_break_16",
"taxes",
"failure_description_section", "failure_description_section",
"error_message", "error_message",
"section_break_14", "section_break_14",
@@ -73,10 +74,12 @@
"label": "User Details" "label": "User Details"
}, },
{ {
"fetch_if_empty": 1,
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Company", "label": "Company",
"options": "Company", "options": "Company",
"read_only": 1,
"reqd": 1 "reqd": 1
}, },
{ {
@@ -85,11 +88,13 @@
}, },
{ {
"fetch_from": "pos_opening_entry.pos_profile", "fetch_from": "pos_opening_entry.pos_profile",
"fetch_if_empty": 1,
"fieldname": "pos_profile", "fieldname": "pos_profile",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "POS Profile", "label": "POS Profile",
"options": "POS Profile", "options": "POS Profile",
"read_only": 1,
"reqd": 1 "reqd": 1
}, },
{ {
@@ -100,16 +105,6 @@
"options": "User", "options": "User",
"reqd": 1 "reqd": 1
}, },
{
"fieldname": "section_break_9",
"fieldtype": "Section Break",
"read_only": 1
},
{
"depends_on": "eval:doc.docstatus==1",
"fieldname": "payment_reconciliation_details",
"fieldtype": "HTML"
},
{ {
"fieldname": "section_break_11", "fieldname": "section_break_11",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@@ -122,7 +117,6 @@
"options": "POS Closing Entry Detail" "options": "POS Closing Entry Detail"
}, },
{ {
"collapsible": 1,
"collapsible_depends_on": "eval:doc.docstatus==0", "collapsible_depends_on": "eval:doc.docstatus==0",
"fieldname": "section_break_13", "fieldname": "section_break_13",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@@ -177,17 +171,12 @@
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
{
"fieldname": "pos_transactions",
"fieldtype": "Table",
"label": "POS Transactions",
"options": "POS Invoice Reference"
},
{ {
"fieldname": "pos_opening_entry", "fieldname": "pos_opening_entry",
"fieldtype": "Link", "fieldtype": "Link",
"label": "POS Opening Entry", "label": "POS Opening Entry",
"options": "POS Opening Entry", "options": "POS Opening Entry",
"print_hide": 1,
"reqd": 1 "reqd": 1
}, },
{ {
@@ -230,10 +219,36 @@
"reqd": 1 "reqd": 1
}, },
{ {
"fieldname": "sales_invoice_transactions", "fieldname": "pos_invoices",
"fieldtype": "Table",
"label": "POS Transactions",
"options": "POS Invoice Reference",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "sales_invoices",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Sales Invoice Transactions", "label": "Sales Invoice Transactions",
"options": "Sales Invoice Reference" "options": "Sales Invoice Reference",
"print_hide": 1,
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "taxes_and_charges_section",
"fieldtype": "Section Break",
"label": "Taxes and Charges"
},
{
"fieldname": "column_break_ywgl",
"fieldtype": "Column Break"
},
{
"fieldname": "total_taxes_and_charges",
"fieldtype": "Currency",
"label": "Total Taxes and Charges",
"read_only": 1
} }
], ],
"grid_page_length": 50, "grid_page_length": 50,
@@ -244,7 +259,7 @@
"link_fieldname": "pos_closing_entry" "link_fieldname": "pos_closing_entry"
} }
], ],
"modified": "2025-03-19 19:49:58.845697", "modified": "2025-06-06 12:00:31.955176",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Closing Entry", "name": "POS Closing Entry",

View File

@@ -4,7 +4,10 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import flt, get_datetime from frappe.query_builder import DocType
from frappe.query_builder import functions as fn
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import flt
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import ( from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
consolidate_pos_invoices, consolidate_pos_invoices,
@@ -41,35 +44,45 @@ class POSClosingEntry(StatusUpdater):
payment_reconciliation: DF.Table[POSClosingEntryDetail] payment_reconciliation: DF.Table[POSClosingEntryDetail]
period_end_date: DF.Datetime period_end_date: DF.Datetime
period_start_date: DF.Datetime period_start_date: DF.Datetime
pos_invoices: DF.Table[POSInvoiceReference]
pos_opening_entry: DF.Link pos_opening_entry: DF.Link
pos_profile: DF.Link pos_profile: DF.Link
pos_transactions: DF.Table[POSInvoiceReference]
posting_date: DF.Date posting_date: DF.Date
posting_time: DF.Time posting_time: DF.Time
sales_invoice_transactions: DF.Table[SalesInvoiceReference] sales_invoices: DF.Table[SalesInvoiceReference]
status: DF.Literal["Draft", "Submitted", "Queued", "Failed", "Cancelled"] status: DF.Literal["Draft", "Submitted", "Queued", "Failed", "Cancelled"]
taxes: DF.Table[POSClosingEntryTaxes] taxes: DF.Table[POSClosingEntryTaxes]
total_quantity: DF.Float total_quantity: DF.Float
total_taxes_and_charges: DF.Currency
user: DF.Link user: DF.Link
# end: auto-generated types # end: auto-generated types
def validate(self): def validate(self):
self.posting_date = self.posting_date or frappe.utils.nowdate() self.set_posting_date_and_time()
self.posting_time = self.posting_time or frappe.utils.nowtime() self.fetch_invoice_type()
self.validate_pos_opening_entry()
self.validate_invoice_mode()
def set_posting_date_and_time(self):
if self.posting_date:
self.posting_date = frappe.utils.nowdate()
if self.posting_time:
self.posting_time = frappe.utils.nowtime()
def fetch_invoice_type(self):
self.invoice_type = frappe.db.get_single_value("POS Settings", "invoice_type")
def validate_pos_opening_entry(self):
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open": if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry")) frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
self.is_pos_using_sales_invoice = frappe.get_single_value( def validate_invoice_mode(self):
"Accounts Settings", "use_sales_invoice_in_pos" if self.invoice_type == "POS Invoice":
)
if self.is_pos_using_sales_invoice == 0:
self.validate_duplicate_pos_invoices() self.validate_duplicate_pos_invoices()
self.validate_pos_invoices() self.validate_pos_invoices()
if self.is_pos_using_sales_invoice == 1: if self.invoice_type == "Sales Invoice":
if len(self.pos_transactions) != 0: if len(self.pos_invoices) != 0:
frappe.throw(_("POS Invoices can't be added when Sales Invoice is enabled")) frappe.throw(_("POS Invoices can't be added when Sales Invoice is enabled"))
self.validate_duplicate_sales_invoices() self.validate_duplicate_sales_invoices()
@@ -77,7 +90,7 @@ class POSClosingEntry(StatusUpdater):
def validate_duplicate_pos_invoices(self): def validate_duplicate_pos_invoices(self):
pos_occurences = {} pos_occurences = {}
for idx, inv in enumerate(self.pos_transactions, 1): for idx, inv in enumerate(self.pos_invoices, 1):
pos_occurences.setdefault(inv.pos_invoice, []).append(idx) pos_occurences.setdefault(inv.pos_invoice, []).append(idx)
error_list = [] error_list = []
@@ -92,7 +105,7 @@ class POSClosingEntry(StatusUpdater):
def validate_pos_invoices(self): def validate_pos_invoices(self):
invalid_rows = [] invalid_rows = []
for d in self.pos_transactions: for d in self.pos_invoices:
invalid_row = {"idx": d.idx} invalid_row = {"idx": d.idx}
pos_invoice = frappe.db.get_values( pos_invoice = frappe.db.get_values(
"POS Invoice", "POS Invoice",
@@ -130,7 +143,7 @@ class POSClosingEntry(StatusUpdater):
def validate_duplicate_sales_invoices(self): def validate_duplicate_sales_invoices(self):
sales_invoice_occurrences = {} sales_invoice_occurrences = {}
for idx, inv in enumerate(self.sales_invoice_transactions, 1): for idx, inv in enumerate(self.sales_invoices, 1):
sales_invoice_occurrences.setdefault(inv.sales_invoice, []).append(idx) sales_invoice_occurrences.setdefault(inv.sales_invoice, []).append(idx)
error_list = [] error_list = []
@@ -145,7 +158,7 @@ class POSClosingEntry(StatusUpdater):
def validate_sales_invoices(self): def validate_sales_invoices(self):
invalid_rows = [] invalid_rows = []
for d in self.sales_invoice_transactions: for d in self.sales_invoices:
invalid_row = {"idx": d.idx} invalid_row = {"idx": d.idx}
sales_invoice = frappe.db.get_values( sales_invoice = frappe.db.get_values(
"Sales Invoice", "Sales Invoice",
@@ -193,14 +206,6 @@ class POSClosingEntry(StatusUpdater):
frappe.throw(error_list, title=_("Invalid Sales Invoices"), as_list=True) frappe.throw(error_list, title=_("Invalid Sales Invoices"), as_list=True)
@frappe.whitelist()
def get_payment_reconciliation_details(self):
currency = frappe.get_cached_value("Company", self.company, "default_currency")
return frappe.render_template(
"erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html",
{"data": self, "currency": currency},
)
def on_submit(self): def on_submit(self):
consolidate_pos_invoices(closing_entry=self) consolidate_pos_invoices(closing_entry=self)
frappe.publish_realtime( frappe.publish_realtime(
@@ -227,7 +232,7 @@ class POSClosingEntry(StatusUpdater):
opening_entry.save() opening_entry.save()
def update_sales_invoices_closing_entry(self, cancel=False): def update_sales_invoices_closing_entry(self, cancel=False):
for d in self.sales_invoice_transactions: for d in self.sales_invoices:
frappe.db.set_value( frappe.db.set_value(
"Sales Invoice", d.sales_invoice, "pos_closing_entry", self.name if not cancel else None "Sales Invoice", d.sales_invoice, "pos_closing_entry", self.name if not cancel else None
) )
@@ -241,50 +246,133 @@ def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist() @frappe.whitelist()
def get_pos_invoices(start, end, pos_profile, user): def get_invoices(start, end, pos_profile, user):
data = frappe.db.sql( invoice_doctype = frappe.db.get_single_value("POS Settings", "invoice_type")
"""
select SalesInvoice = DocType("Sales Invoice")
name, timestamp(posting_date, posting_time) as "timestamp" sales_inv_query = (
from frappe.qb.from_(SalesInvoice)
`tabPOS Invoice` .select(
where SalesInvoice.name,
owner = %s and docstatus = 1 and pos_profile = %s and ifnull(consolidated_invoice,'') = '' SalesInvoice.customer,
""", SalesInvoice.posting_date,
(user, pos_profile), SalesInvoice.grand_total,
as_dict=1, SalesInvoice.net_total,
SalesInvoice.total_qty,
SalesInvoice.total_taxes_and_charges,
fn.Timestamp(SalesInvoice.posting_date, SalesInvoice.posting_time).as_("timestamp"),
ConstantColumn("Sales Invoice").as_("doctype"),
SalesInvoice.change_amount,
SalesInvoice.account_for_change_amount,
)
.where(
(SalesInvoice.owner == user)
& (SalesInvoice.docstatus == 1)
& (SalesInvoice.is_pos == 1)
& (SalesInvoice.pos_profile == pos_profile)
& (SalesInvoice.is_created_using_pos == 1)
& fn.IfNull(SalesInvoice.pos_closing_entry, "").eq("")
& (
(fn.Timestamp(SalesInvoice.posting_date, SalesInvoice.posting_time) >= start)
& (fn.Timestamp(SalesInvoice.posting_date, SalesInvoice.posting_time) <= end)
)
)
) )
data = list(filter(lambda d: get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end), data)) query = sales_inv_query
# need to get taxes and payments so can't avoid get_doc
data = [frappe.get_doc("POS Invoice", d.name).as_dict() for d in data] if invoice_doctype == "POS Invoice":
POSInvoice = DocType("POS Invoice")
pos_inv_query = (
frappe.qb.from_(POSInvoice)
.select(
POSInvoice.name,
POSInvoice.customer,
POSInvoice.posting_date,
POSInvoice.grand_total,
POSInvoice.net_total,
POSInvoice.total_qty,
POSInvoice.total_taxes_and_charges,
fn.Timestamp(POSInvoice.posting_date, POSInvoice.posting_time).as_("timestamp"),
ConstantColumn("POS Invoice").as_("doctype"),
POSInvoice.change_amount,
POSInvoice.account_for_change_amount,
)
.where(
(POSInvoice.owner == user)
& (POSInvoice.docstatus == 1)
& (POSInvoice.pos_profile == pos_profile)
& (
(fn.Timestamp(POSInvoice.posting_date, POSInvoice.posting_time) >= start)
& (fn.Timestamp(POSInvoice.posting_date, POSInvoice.posting_time) <= end)
)
& fn.IfNull(POSInvoice.consolidated_invoice, "").eq("")
)
)
query = query + pos_inv_query
query = query.orderby(query.timestamp)
invoices = query.run(as_dict=1)
data = {"invoices": invoices, "payments": get_payments(invoices), "taxes": get_taxes(invoices)}
return data return data
@frappe.whitelist() def get_payments(invoices):
def get_sales_invoices(start, end, pos_profile, user): if not len(invoices):
data = frappe.db.sql( return
"""
select
name, timestamp(posting_date, posting_time) as "timestamp"
from
`tabSales Invoice`
where
owner = %s
and docstatus = 1
and is_pos = 1
and pos_profile = %s
and is_created_using_pos = 1
and ifnull(pos_closing_entry,'') = ''
""",
(user, pos_profile),
as_dict=1,
)
data = [d for d in data if get_datetime(start) <= get_datetime(d.timestamp) <= get_datetime(end)] invoices_name = [d.name for d in invoices]
# need to get taxes and payments so can't avoid get_doc
data = [frappe.get_doc("Sales Invoice", d.name).as_dict() for d in data] SalesInvoicePayment = DocType("Sales Invoice Payment")
query = (
frappe.qb.from_(SalesInvoicePayment)
.where(
(SalesInvoicePayment.parenttype.isin(["Sales Invoice", "POS Invoice"]))
& (SalesInvoicePayment.parent.isin(invoices_name))
)
.groupby(SalesInvoicePayment.mode_of_payment)
.select(
SalesInvoicePayment.mode_of_payment,
SalesInvoicePayment.account,
fn.Sum(SalesInvoicePayment.amount).as_("amount"),
)
)
data = query.run(as_dict=1)
change_amount_by_account = {}
for d in invoices:
change_amount_by_account.setdefault(d.account_for_change_amount, 0)
change_amount_by_account[d.account_for_change_amount] += flt(d.change_amount)
for d in data:
if change_amount_by_account.get(d.account):
d.amount -= flt(change_amount_by_account.get(d.account))
return data
def get_taxes(invoices):
if not len(invoices):
return
invoices_name = [d.name for d in invoices]
SalesInvoiceTaxesCharges = DocType("Sales Taxes and Charges")
query = (
frappe.qb.from_(SalesInvoiceTaxesCharges)
.where(
(SalesInvoiceTaxesCharges.parenttype.isin(["Sales Invoice", "POS Invoice"]))
& (SalesInvoiceTaxesCharges.parent.isin(invoices_name))
)
.groupby(SalesInvoiceTaxesCharges.account_head)
.select(
SalesInvoiceTaxesCharges.account_head,
fn.Sum(SalesInvoiceTaxesCharges.tax_amount_after_discount_amount).as_("tax_amount"),
)
)
data = query.run(as_dict=1)
return data return data
@@ -300,97 +388,53 @@ def make_closing_entry_from_opening(opening_entry):
closing_entry.grand_total = 0 closing_entry.grand_total = 0
closing_entry.net_total = 0 closing_entry.net_total = 0
closing_entry.total_quantity = 0 closing_entry.total_quantity = 0
closing_entry.total_taxes_and_charges = 0
is_pos_using_sales_invoice = frappe.get_single_value("Accounts Settings", "use_sales_invoice_in_pos") data = get_invoices(
pos_invoices = (
get_pos_invoices(
closing_entry.period_start_date,
closing_entry.period_end_date,
closing_entry.pos_profile,
closing_entry.user,
)
if is_pos_using_sales_invoice == 0
else []
)
sales_invoices = get_sales_invoices(
closing_entry.period_start_date, closing_entry.period_start_date,
closing_entry.period_end_date, closing_entry.period_end_date,
closing_entry.pos_profile, closing_entry.pos_profile,
closing_entry.user, closing_entry.user,
) )
pos_transactions = [] pos_invoices = []
sales_invoice_transactions = [] sales_invoices = []
taxes = [] taxes = [
payments = [] frappe._dict({"account_head": tx.account_head, "amount": tx.tax_amount}) for tx in data.get("taxes")
for detail in opening_entry.balance_details: ]
payments.append( payments = [
frappe._dict( frappe._dict(
{ {
"mode_of_payment": detail.mode_of_payment, "mode_of_payment": p.mode_of_payment,
"opening_amount": detail.opening_amount, "opening_amount": 0,
"expected_amount": detail.opening_amount, "expected_amount": p.amount,
} }
)
) )
for p in data.get("payments")
]
for d in pos_invoices: for d in data.get("invoices"):
pos_transactions.append( invoice = "pos_invoice" if d.doctype == "POS Invoice" else "sales_invoice"
frappe._dict( invoice_data = frappe._dict(
{ {
"pos_invoice": d.name, invoice: d.name,
"posting_date": d.posting_date, "posting_date": d.posting_date,
"grand_total": d.grand_total, "grand_total": d.grand_total,
"customer": d.customer, "customer": d.customer,
} }
)
) )
if d.doctype == "POS Invoice":
pos_invoices.append(invoice_data)
else:
sales_invoices.append(invoice_data)
for d in sales_invoices:
sales_invoice_transactions.append(
frappe._dict(
{
"sales_invoice": d.name,
"posting_date": d.posting_date,
"grand_total": d.grand_total,
"customer": d.customer,
}
)
)
for d in [*pos_invoices, *sales_invoices]:
closing_entry.grand_total += flt(d.grand_total) closing_entry.grand_total += flt(d.grand_total)
closing_entry.net_total += flt(d.net_total) closing_entry.net_total += flt(d.net_total)
closing_entry.total_quantity += flt(d.total_qty) closing_entry.total_quantity += flt(d.total_qty)
closing_entry.total_taxes_and_charges += flt(d.total_taxes_and_charges)
for t in d.taxes: closing_entry.set("pos_invoices", pos_invoices)
existing_tax = [tx for tx in taxes if tx.account_head == t.account_head and tx.rate == t.rate] closing_entry.set("sales_invoices", sales_invoices)
if existing_tax:
existing_tax[0].amount += flt(t.tax_amount)
else:
taxes.append(
frappe._dict({"account_head": t.account_head, "rate": t.rate, "amount": t.tax_amount})
)
for p in d.payments:
existing_pay = [pay for pay in payments if pay.mode_of_payment == p.mode_of_payment]
if existing_pay:
existing_pay[0].expected_amount += flt(p.amount)
else:
payments.append(
frappe._dict(
{
"mode_of_payment": p.mode_of_payment,
"opening_amount": 0,
"expected_amount": p.amount,
}
)
)
closing_entry.set("pos_transactions", pos_transactions)
closing_entry.set("sales_invoice_transactions", sales_invoice_transactions)
closing_entry.set("payment_reconciliation", payments) closing_entry.set("payment_reconciliation", payments)
closing_entry.set("taxes", taxes) closing_entry.set("taxes", taxes)

View File

@@ -12,10 +12,10 @@ from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension imp
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import ( from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import (
make_closing_entry_from_opening, make_closing_entry_from_opening,
) )
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.selling.page.point_of_sale.point_of_sale import get_items from erpnext.selling.page.point_of_sale.point_of_sale import get_items
from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import ( from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
@@ -25,8 +25,18 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
class TestPOSClosingEntry(IntegrationTestCase): class TestPOSClosingEntry(IntegrationTestCase):
@classmethod
def setUpClass(cls):
frappe.db.sql("delete from `tabPOS Opening Entry`")
cls.enterClassContext(cls.change_settings("POS Settings", {"invoice_type": "POS Invoice"}))
@classmethod
def tearDownClass(cls):
frappe.db.sql("delete from `tabPOS Opening Entry`")
def setUp(self): def setUp(self):
# Make stock available for POS Sales # Make stock available for POS Sales
frappe.db.sql("delete from `tabPOS Opening Entry`")
make_stock_entry(target="_Test Warehouse - _TC", qty=2, basic_rate=100) make_stock_entry(target="_Test Warehouse - _TC", qty=2, basic_rate=100)
def tearDown(self): def tearDown(self):
@@ -82,6 +92,8 @@ class TestPOSClosingEntry(IntegrationTestCase):
""" """
Test if quantity is calculated correctly for an item in POS Closing Entry Test if quantity is calculated correctly for an item in POS Closing Entry
""" """
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
test_user, pos_profile = init_user_and_profile() test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name) opening_entry = create_opening_entry(pos_profile, test_user.name)
@@ -200,9 +212,6 @@ class TestPOSClosingEntry(IntegrationTestCase):
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import ( from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import (
init_user_and_profile, init_user_and_profile,
) )
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
consolidate_pos_invoices,
)
from erpnext.stock.doctype.batch.batch import get_batch_qty from erpnext.stock.doctype.batch.batch import get_batch_qty
frappe.db.sql("delete from `tabPOS Invoice`") frappe.db.sql("delete from `tabPOS Invoice`")
@@ -293,45 +302,171 @@ class TestPOSClosingEntry(IntegrationTestCase):
batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code) batch_qty_with_pos = get_batch_qty(batch_no, "_Test Warehouse - _TC", item_code)
self.assertEqual(batch_qty_with_pos, 10.0) self.assertEqual(batch_qty_with_pos, 10.0)
@IntegrationTestCase.change_settings("POS Settings", {"invoice_type": "Sales Invoice"})
def test_closing_entries_with_sales_invoice(self): def test_closing_entries_with_sales_invoice(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
pos_si = create_sales_invoice(
qty=10, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1
)
pos_si.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000})
pos_si.save()
pos_si.submit()
pos_si2 = create_sales_invoice(
qty=5, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=11
)
pos_si2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000})
pos_si2.save()
pos_si2.submit()
pcv_doc = make_closing_entry_from_opening(opening_entry)
payment = pcv_doc.payment_reconciliation[0]
self.assertEqual(payment.mode_of_payment, "Cash")
for d in pcv_doc.payment_reconciliation:
if d.mode_of_payment == "Cash":
d.closing_amount = 1500
pcv_doc.submit()
self.assertEqual(pcv_doc.total_quantity, 15)
self.assertEqual(pcv_doc.net_total, 1500)
pos_si2.reload()
self.assertEqual(pos_si2.pos_closing_entry, pcv_doc.name)
def test_sales_invoice_in_pos_invoice_mode(self):
"""
Test Sales Invoice and Return Sales Invoice creation during POS Invoice mode.
"""
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
test_user, pos_profile = init_user_and_profile() test_user, pos_profile = init_user_and_profile()
# Deleting all opening entry
frappe.db.sql("delete from `tabPOS Opening Entry`")
with self.change_settings("Accounts Settings", {"use_sales_invoice_in_pos": 1}): with self.change_settings("POS Settings", {"invoice_type": "Sales Invoice"}):
opening_entry = create_opening_entry(pos_profile, test_user.name) opening_entry1 = create_opening_entry(pos_profile, test_user.name)
pos_si = create_sales_invoice(qty=10, do_not_save=1) pos_si1, pos_si2 = create_multiple_sales_invoices(pos_profile)
pos_si.is_pos = 1
pos_si.pos_profile = pos_profile.name
pos_si.is_created_using_pos = 1
pos_si.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000})
pos_si.save()
pos_si.submit()
pos_si2 = create_sales_invoice(qty=5, do_not_save=1) pos_inv = create_pos_invoice(rate=100, do_not_save=1)
pos_si2.is_pos = 1 pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_si2.pos_profile = pos_profile.name self.assertRaises(frappe.ValidationError, pos_inv.save)
pos_si2.is_created_using_pos = 1
pos_si2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000})
pos_si2.save()
pos_si2.submit()
pcv_doc = make_closing_entry_from_opening(opening_entry) pcv_doc1 = make_closing_entry_from_opening(opening_entry1)
payment = pcv_doc.payment_reconciliation[0] for d in pcv_doc1.payment_reconciliation:
self.assertEqual(payment.mode_of_payment, "Cash")
for d in pcv_doc.payment_reconciliation:
if d.mode_of_payment == "Cash": if d.mode_of_payment == "Cash":
d.closing_amount = 1500 d.closing_amount = 300
pcv_doc.submit() pcv_doc1.submit()
self.assertTrue(pcv_doc1.name)
self.assertEqual(pcv_doc.total_quantity, 15) pos_si1.reload()
self.assertEqual(pcv_doc.net_total, 1500) pos_si2.reload()
self.assertEqual(pos_si1.pos_closing_entry, pcv_doc1.name)
self.assertEqual(pos_si2.pos_closing_entry, pcv_doc1.name)
with self.change_settings("POS Settings", {"invoice_type": "POS Invoice"}):
opening_entry2 = create_opening_entry(pos_profile, test_user.name)
pos_inv1, pos_inv2 = create_multiple_pos_invoices(pos_profile)
# Trying to create Sales Invoice when invoice_type is set to POS Invoice.
pos_si3 = create_sales_invoice(
qty=1, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1
)
pos_si3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
self.assertRaises(frappe.ValidationError, pos_si3.save)
# Trying to create Return Sales Invoice.
pos_rsi1 = make_sales_return(pos_si1.name)
pos_rsi1.save()
pos_rsi1.submit()
self.assertEqual(pos_rsi1.paid_amount, -100)
pcv_doc2 = make_closing_entry_from_opening(opening_entry2)
pcv_doc2.submit()
self.assertTrue(pcv_doc2.name)
pos_rsi1.reload()
self.assertEqual(pos_rsi1.pos_closing_entry, pcv_doc2.name)
self.assertIn(pos_inv1.name, [d.pos_invoice for d in pcv_doc2.pos_invoices])
self.assertNotIn(pos_inv2.name, [d.sales_invoice for d in pcv_doc2.sales_invoices])
self.assertIn(pos_rsi1.name, [d.sales_invoice for d in pcv_doc2.sales_invoices])
self.assertEqual(pcv_doc2.grand_total, 200)
def test_pos_invoice_in_sales_invoice_mode(self):
"""
Test POS Invoice and Return POS Invoice creation during Sales Invoice mode.
"""
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
test_user, pos_profile = init_user_and_profile()
with self.change_settings("POS Settings", {"invoice_type": "POS Invoice"}):
opening_entry1 = create_opening_entry(pos_profile, test_user.name)
pos_inv1, pos_inv2 = create_multiple_pos_invoices(pos_profile)
# Trying to create Sales Invoice when invoice_type is set to POS Invoice.
pos_sinv = create_sales_invoice(
qty=1, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1
)
pos_sinv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
self.assertRaises(frappe.ValidationError, pos_sinv.save)
pcv_doc1 = make_closing_entry_from_opening(opening_entry1)
for d in pcv_doc1.payment_reconciliation:
if d.mode_of_payment == "Cash":
d.closing_amount = 300
pcv_doc1.submit()
self.assertTrue(pcv_doc1.name)
self.assertIn(pos_inv1.name, [d.pos_invoice for d in pcv_doc1.pos_invoices])
self.assertEqual(pcv_doc1.grand_total, 300)
with self.change_settings("POS Settings", {"invoice_type": "Sales Invoice"}):
opening_entry2 = create_opening_entry(pos_profile, test_user.name)
pos_si1, pos_si2 = create_multiple_sales_invoices(pos_profile)
pos_inv3 = create_pos_invoice(rate=100, do_not_save=1)
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
self.assertRaises(frappe.ValidationError, pos_inv3.save)
# Creating Return POS Invoice
pos_rinv2 = make_sales_return(pos_inv2.name)
pos_rinv2.save()
pos_rinv2.submit()
pos_rinv2.reload()
self.assertIsNotNone(pos_rinv2.consolidated_invoice)
# Getting Sales Invoice created during POS Invoice submission.
pos_rinv2_si = frappe.get_doc("Sales Invoice", pos_rinv2.consolidated_invoice)
self.assertEqual(pos_rinv2_si.is_return, 1)
self.assertEqual(pos_rinv2_si.paid_amount, -200)
pcv_doc2 = make_closing_entry_from_opening(opening_entry2)
for d in pcv_doc1.payment_reconciliation:
if d.mode_of_payment == "Cash":
d.closing_amount = 100
pcv_doc2.submit()
self.assertTrue(pcv_doc2.name)
pos_si1.reload()
pos_si2.reload()
pos_rinv2_si.reload()
self.assertEqual(pos_si2.pos_closing_entry, pcv_doc2.name)
self.assertEqual(pos_rinv2_si.pos_closing_entry, pcv_doc2.name)
def init_user_and_profile(**args): def init_user_and_profile(**args):
@@ -367,3 +502,31 @@ def get_test_item_qty(pos_profile):
"actual_qty" "actual_qty"
) )
return test_item_qty return test_item_qty
def create_multiple_sales_invoices(pos_profile):
pos_si1 = create_sales_invoice(qty=1, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1)
pos_si1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_si1.save()
pos_si1.submit()
pos_si2 = create_sales_invoice(qty=2, is_created_using_pos=1, pos_profile=pos_profile.name, do_not_save=1)
pos_si2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 200})
pos_si2.save()
pos_si2.submit()
return pos_si1, pos_si2
def create_multiple_pos_invoices(pos_profile):
pos_inv1 = create_pos_invoice(pos_profile=pos_profile.name, rate=100, do_not_save=1)
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_inv1.save()
pos_inv1.submit()
pos_inv2 = create_pos_invoice(pos_profile=pos_profile.name, qty=2, do_not_save=1)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 200})
pos_inv2.save()
pos_inv2.submit()
return pos_inv1, pos_inv2

View File

@@ -6,17 +6,9 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"account_head", "account_head",
"rate",
"amount" "amount"
], ],
"fields": [ "fields": [
{
"fieldname": "rate",
"fieldtype": "Percent",
"in_list_view": 1,
"label": "Tax Rate",
"read_only": 1
},
{ {
"fieldname": "amount", "fieldname": "amount",
"fieldtype": "Currency", "fieldtype": "Currency",
@@ -35,13 +27,14 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2024-03-27 13:10:14.420657", "modified": "2025-06-06 11:54:02.414461",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Closing Entry Taxes", "name": "POS Closing Entry Taxes",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,
"row_format": "Dynamic",
"sort_field": "creation", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [], "states": [],

View File

@@ -19,7 +19,6 @@ class POSClosingEntryTaxes(Document):
parent: DF.Data parent: DF.Data
parentfield: DF.Data parentfield: DF.Data
parenttype: DF.Data parenttype: DF.Data
rate: DF.Percent
# end: auto-generated types # end: auto-generated types
pass pass

View File

@@ -243,7 +243,7 @@ class POSInvoice(SalesInvoice):
update_coupon_code_count(self.coupon_code, "used") update_coupon_code_count(self.coupon_code, "used")
self.clear_unallocated_mode_of_payments() self.clear_unallocated_mode_of_payments()
if self.is_return and self.is_pos_using_sales_invoice: if self.is_return and self.invoice_type_in_pos == "Sales Invoice":
self.create_and_add_consolidated_sales_invoice() self.create_and_add_consolidated_sales_invoice()
def before_cancel(self): def before_cancel(self):
@@ -424,10 +424,8 @@ class POSInvoice(SalesInvoice):
) )
def validate_is_pos_using_sales_invoice(self): def validate_is_pos_using_sales_invoice(self):
self.is_pos_using_sales_invoice = frappe.get_single_value( self.invoice_type_in_pos = frappe.db.get_single_value("POS Settings", "invoice_type")
"Accounts Settings", "use_sales_invoice_in_pos" if self.invoice_type_in_pos == "Sales Invoice" and not self.is_return:
)
if self.is_pos_using_sales_invoice and not self.is_return:
frappe.throw(_("Sales Invoice mode is activated in POS. Please create Sales Invoice instead.")) frappe.throw(_("Sales Invoice mode is activated in POS. Please create Sales Invoice instead."))
def validate_serialised_or_batched_item(self): def validate_serialised_or_batched_item(self):

View File

@@ -29,6 +29,7 @@ class TestPOSInvoice(IntegrationTestCase):
def setUpClass(cls): def setUpClass(cls):
super().setUpClass() super().setUpClass()
cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0)) cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0))
cls.enterClassContext(cls.change_settings("POS Settings", invoice_type="POS Invoice"))
make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100) make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100)
frappe.db.sql("delete from `tabTax Rule`") frappe.db.sql("delete from `tabTax Rule`")
@@ -36,10 +37,16 @@ class TestPOSInvoice(IntegrationTestCase):
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
cls.test_user, cls.pos_profile = init_user_and_profile() cls.test_user, cls.pos_profile = init_user_and_profile()
create_opening_entry(cls.pos_profile, cls.test_user.name) cls.opening_entry = create_opening_entry(cls.pos_profile, cls.test_user.name)
mode_of_payment = frappe.get_doc("Mode of Payment", "Bank Draft") mode_of_payment = frappe.get_doc("Mode of Payment", "Bank Draft")
set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank - _TC") set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank - _TC")
@classmethod
def tearDownClass(cls):
frappe.db.sql("delete from `tabPOS Invoice`")
opening_entry_doc = frappe.get_doc("POS Opening Entry", cls.opening_entry.name)
opening_entry_doc.cancel()
def tearDown(self): def tearDown(self):
if frappe.session.user != "Administrator": if frappe.session.user != "Administrator":
frappe.set_user("Administrator") frappe.set_user("Administrator")

View File

@@ -491,7 +491,7 @@ def split_invoices_by_accounting_dimension(pos_invoices):
def consolidate_pos_invoices(pos_invoices=None, closing_entry=None): def consolidate_pos_invoices(pos_invoices=None, closing_entry=None):
invoices = pos_invoices or (closing_entry and closing_entry.get("pos_transactions")) invoices = pos_invoices or (closing_entry and closing_entry.get("pos_invoices"))
if frappe.flags.in_test and not invoices: if frappe.flags.in_test and not invoices:
invoices = get_all_unconsolidated_invoices() invoices = get_all_unconsolidated_invoices()
@@ -509,7 +509,7 @@ def unconsolidate_pos_invoices(closing_entry):
"POS Invoice Merge Log", filters={"pos_closing_entry": closing_entry.name}, pluck="name" "POS Invoice Merge Log", filters={"pos_closing_entry": closing_entry.name}, pluck="name"
) )
if len(closing_entry.pos_transactions) >= 10: if len(closing_entry.pos_invoices) >= 10:
closing_entry.set_status(update=True, status="Queued") closing_entry.set_status(update=True, status="Queued")
enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry) enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry)
else: else:

View File

@@ -5,12 +5,16 @@ import json
import frappe import frappe
from frappe.tests import IntegrationTestCase from frappe.tests import IntegrationTestCase
from erpnext.accounts.doctype.mode_of_payment.test_mode_of_payment import (
set_default_account_for_mode_of_payment,
)
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import (
make_closing_entry_from_opening,
)
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import ( from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
consolidate_pos_invoices,
)
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import ( from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
get_serial_nos_from_bundle, get_serial_nos_from_bundle,
) )
@@ -21,241 +25,310 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super().setUpClass() super().setUpClass()
frappe.db.sql("delete from `tabPOS Opening Entry`")
cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0)) cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0))
cls.enterClassContext(cls.change_settings("POS Settings", invoice_type="POS Invoice"))
mode_of_payment = frappe.get_doc("Mode of Payment", "Bank Draft")
set_default_account_for_mode_of_payment(mode_of_payment, "_Test Company", "_Test Bank - _TC")
def setUp(self):
frappe.db.sql("delete from `tabPOS Invoice`")
def tearDown(self):
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
def test_consolidated_invoice_creation(self): def test_consolidated_invoice_creation(self):
frappe.db.sql("delete from `tabPOS Invoice`") test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
try: pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
test_user, pos_profile = init_user_and_profile() pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
pos_inv.save()
pos_inv.submit()
pos_inv = create_pos_invoice(rate=300, do_not_submit=1) pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300}) pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
pos_inv.save() pos_inv2.save()
pos_inv.submit() pos_inv2.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200}) pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
pos_inv2.save() pos_inv3.save()
pos_inv2.submit() pos_inv3.submit()
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1) closing_entry = make_closing_entry_from_opening(opening_entry)
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300}) closing_entry.insert()
pos_inv3.save() closing_entry.submit()
pos_inv3.submit()
consolidate_pos_invoices() pos_inv.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
pos_inv.load_from_db() pos_inv3.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
pos_inv3.load_from_db() self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
def test_consolidated_credit_note_creation(self): def test_consolidated_credit_note_creation(self):
frappe.db.sql("delete from `tabPOS Invoice`") test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
try: pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
test_user, pos_profile = init_user_and_profile() pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
pos_inv.save()
pos_inv.submit()
pos_inv = create_pos_invoice(rate=300, do_not_submit=1) pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300}) pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
pos_inv.save() pos_inv2.save()
pos_inv.submit() pos_inv2.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200}) pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
pos_inv2.save() pos_inv3.save()
pos_inv2.submit() pos_inv3.submit()
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1) pos_inv_cn = make_sales_return(pos_inv.name)
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300}) pos_inv_cn.set("payments", [])
pos_inv3.save() pos_inv_cn.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -100})
pos_inv3.submit() pos_inv_cn.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": -200}
)
pos_inv_cn.paid_amount = -300
pos_inv_cn.submit()
pos_inv_cn = make_sales_return(pos_inv.name) closing_entry = make_closing_entry_from_opening(opening_entry)
pos_inv_cn.set("payments", []) closing_entry.insert()
pos_inv_cn.append( closing_entry.submit()
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -100}
)
pos_inv_cn.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": -200}
)
pos_inv_cn.paid_amount = -300
pos_inv_cn.submit()
consolidate_pos_invoices() pos_inv.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
pos_inv.load_from_db() pos_inv3.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
pos_inv3.load_from_db() pos_inv_cn.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice)) self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
consolidated_credit_note = frappe.get_doc("Sales Invoice", pos_inv_cn.consolidated_invoice)
pos_inv_cn.load_from_db() self.assertEqual(consolidated_credit_note.is_return, 1)
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice)) self.assertEqual(consolidated_credit_note.payments[0].mode_of_payment, "Cash")
consolidated_credit_note = frappe.get_doc("Sales Invoice", pos_inv_cn.consolidated_invoice) self.assertEqual(consolidated_credit_note.payments[0].amount, -100)
self.assertEqual(consolidated_credit_note.is_return, 1) self.assertEqual(consolidated_credit_note.payments[1].mode_of_payment, "Bank Draft")
self.assertEqual(consolidated_credit_note.payments[0].mode_of_payment, "Cash") self.assertEqual(consolidated_credit_note.payments[1].amount, -200)
self.assertEqual(consolidated_credit_note.payments[0].amount, -100)
self.assertEqual(consolidated_credit_note.payments[1].mode_of_payment, "Bank Draft")
self.assertEqual(consolidated_credit_note.payments[1].amount, -200)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
def test_consolidated_invoice_item_taxes(self): def test_consolidated_invoice_item_taxes(self):
frappe.db.sql("delete from `tabPOS Invoice`") test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
try: inv = create_pos_invoice(qty=1, rate=100, do_not_save=True)
inv = create_pos_invoice(qty=1, rate=100, do_not_save=True)
inv.append( inv.append(
"taxes", "taxes",
{ {
"account_head": "_Test Account VAT - _TC", "account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total", "charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"description": "VAT", "description": "VAT",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"rate": 9, "rate": 9,
}, },
) )
inv.insert() inv.insert()
inv.payments[0].amount = inv.grand_total inv.payments[0].amount = inv.grand_total
inv.save() inv.save()
inv.submit() inv.submit()
inv2 = create_pos_invoice(qty=1, rate=100, do_not_save=True) inv2 = create_pos_invoice(qty=1, rate=100, do_not_save=True)
inv2.get("items")[0].item_code = "_Test Item 2" inv2.get("items")[0].item_code = "_Test Item 2"
inv2.append( inv2.append(
"taxes", "taxes",
{ {
"account_head": "_Test Account VAT - _TC", "account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total", "charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"description": "VAT", "description": "VAT",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"rate": 5, "rate": 5,
}, },
) )
inv2.insert() inv2.insert()
inv2.payments[0].amount = inv.grand_total inv2.payments[0].amount = inv.grand_total
inv2.save() inv2.save()
inv2.submit() inv2.submit()
consolidate_pos_invoices() closing_entry = make_closing_entry_from_opening(opening_entry)
inv.load_from_db() closing_entry.insert()
closing_entry.submit()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice) inv.load_from_db()
item_wise_tax_detail = json.loads(consolidated_invoice.get("taxes")[0].item_wise_tax_detail)
expected_item_wise_tax_detail = { consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
"_Test Item": { item_wise_tax_detail = json.loads(consolidated_invoice.get("taxes")[0].item_wise_tax_detail)
"tax_rate": 9, expected_item_wise_tax_detail = {
"tax_amount": 9, "_Test Item": {
"net_amount": 100, "tax_rate": 9,
}, "tax_amount": 9,
"_Test Item 2": { "net_amount": 100,
"tax_rate": 5, },
"tax_amount": 5, "_Test Item 2": {
"net_amount": 100, "tax_rate": 5,
}, "tax_amount": 5,
} "net_amount": 100,
self.assertEqual(item_wise_tax_detail, expected_item_wise_tax_detail) },
finally: }
frappe.set_user("Administrator") self.assertEqual(item_wise_tax_detail, expected_item_wise_tax_detail)
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
def test_consolidation_round_off_error_1(self): def test_consolidation_round_off_error_1(self):
""" """
Test round off error in consolidated invoice creation if POS Invoice has inclusive tax Test round off error in consolidated invoice creation if POS Invoice has inclusive tax
""" """
frappe.db.sql("delete from `tabPOS Invoice`") make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
try: test_user, pos_profile = init_user_and_profile()
make_stock_entry( opening_entry = create_opening_entry(pos_profile, test_user.name)
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
init_user_and_profile() inv = create_pos_invoice(qty=3, rate=10000, do_not_save=True)
inv.append(
"taxes",
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"rate": 7.5,
"included_in_print_rate": 1,
},
)
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 30000})
inv.insert()
inv.submit()
inv = create_pos_invoice(qty=3, rate=10000, do_not_save=True) inv2 = create_pos_invoice(qty=3, rate=10000, do_not_save=True)
inv.append( inv2.append(
"taxes", "taxes",
{ {
"account_head": "_Test Account VAT - _TC", "account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total", "charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"description": "VAT", "description": "VAT",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"rate": 7.5, "rate": 7.5,
"included_in_print_rate": 1, "included_in_print_rate": 1,
}, },
) )
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 30000}) inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 30000})
inv.insert() inv2.insert()
inv.submit() inv2.submit()
inv2 = create_pos_invoice(qty=3, rate=10000, do_not_save=True) closing_entry = make_closing_entry_from_opening(opening_entry)
inv2.append( closing_entry.insert()
"taxes", closing_entry.submit()
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"rate": 7.5,
"included_in_print_rate": 1,
},
)
inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 30000})
inv2.insert()
inv2.submit()
consolidate_pos_invoices() inv.load_from_db()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
inv.load_from_db() self.assertEqual(consolidated_invoice.outstanding_amount, 0)
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice) self.assertEqual(consolidated_invoice.status, "Paid")
self.assertEqual(consolidated_invoice.outstanding_amount, 0)
self.assertEqual(consolidated_invoice.status, "Paid")
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
def test_consolidation_round_off_error_2(self): def test_consolidation_round_off_error_2(self):
""" """
Test the same case as above but with an Unpaid POS Invoice Test the same case as above but with an Unpaid POS Invoice
""" """
frappe.db.sql("delete from `tabPOS Invoice`") make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
try: test_user, pos_profile = init_user_and_profile()
make_stock_entry( opening_entry = create_opening_entry(pos_profile, test_user.name)
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
init_user_and_profile() inv = create_pos_invoice(qty=6, rate=10000, do_not_save=True)
inv.append(
"taxes",
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"rate": 7.5,
"included_in_print_rate": 1,
},
)
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60000})
inv.insert()
inv.submit()
inv = create_pos_invoice(qty=6, rate=10000, do_not_save=True) inv2 = create_pos_invoice(qty=6, rate=10000, do_not_save=True)
inv2.append(
"taxes",
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"rate": 7.5,
"included_in_print_rate": 1,
},
)
inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60000})
inv2.insert()
inv2.submit()
inv3 = create_pos_invoice(qty=3, rate=600, do_not_save=True)
inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1800})
inv3.insert()
inv3.submit()
closing_entry = make_closing_entry_from_opening(opening_entry)
closing_entry.insert()
closing_entry.submit()
inv.load_from_db()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertNotEqual(consolidated_invoice.outstanding_amount, 800)
self.assertEqual(consolidated_invoice.status, "Paid")
@IntegrationTestCase.change_settings(
"System Settings", {"number_format": "#,###.###", "currency_precision": 3, "float_precision": 3}
)
def test_consolidation_round_off_error_3(self):
make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
test_user, pos_profile = init_user_and_profile()
opening_entry = create_opening_entry(pos_profile, test_user.name)
item_rates = [69, 59, 29]
for _i in [1, 2]:
inv = create_pos_invoice(is_return=1, do_not_save=1)
inv.items = []
for rate in item_rates:
inv.append(
"items",
{
"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC",
"qty": -1,
"rate": rate,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
},
)
inv.append( inv.append(
"taxes", "taxes",
{ {
@@ -264,146 +337,56 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"description": "VAT", "description": "VAT",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"rate": 7.5, "rate": 15,
"included_in_print_rate": 1, "included_in_print_rate": 1,
}, },
) )
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60000}) inv.payments = []
inv.insert() inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -157})
inv.paid_amount = -157
inv.save()
inv.submit() inv.submit()
inv2 = create_pos_invoice(qty=6, rate=10000, do_not_save=True) closing_entry = make_closing_entry_from_opening(opening_entry)
inv2.append( closing_entry.insert()
"taxes", closing_entry.submit()
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"rate": 7.5,
"included_in_print_rate": 1,
},
)
inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60000})
inv2.insert()
inv2.submit()
inv3 = create_pos_invoice(qty=3, rate=600, do_not_save=True) inv.load_from_db()
inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1800}) consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
inv3.insert() self.assertEqual(consolidated_invoice.status, "Return")
inv3.submit() self.assertEqual(consolidated_invoice.rounding_adjustment, -0.002)
consolidate_pos_invoices()
inv.load_from_db()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertNotEqual(consolidated_invoice.outstanding_amount, 800)
self.assertEqual(consolidated_invoice.status, "Paid")
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
@IntegrationTestCase.change_settings(
"System Settings", {"number_format": "#,###.###", "currency_precision": 3, "float_precision": 3}
)
def test_consolidation_round_off_error_3(self):
frappe.db.sql("delete from `tabPOS Invoice`")
try:
make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
init_user_and_profile()
item_rates = [69, 59, 29]
for _i in [1, 2]:
inv = create_pos_invoice(is_return=1, do_not_save=1)
inv.items = []
for rate in item_rates:
inv.append(
"items",
{
"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC",
"qty": -1,
"rate": rate,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
},
)
inv.append(
"taxes",
{
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"rate": 15,
"included_in_print_rate": 1,
},
)
inv.payments = []
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -157})
inv.paid_amount = -157
inv.save()
inv.submit()
consolidate_pos_invoices()
inv.load_from_db()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.status, "Return")
self.assertEqual(consolidated_invoice.rounding_adjustment, -0.002)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
def test_consolidation_rounding_adjustment(self): def test_consolidation_rounding_adjustment(self):
""" """
Test if the rounding adjustment is calculated correctly Test if the rounding adjustment is calculated correctly
""" """
frappe.db.sql("delete from `tabPOS Invoice`") make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
try: test_user, pos_profile = init_user_and_profile()
make_stock_entry( opening_entry = create_opening_entry(pos_profile, test_user.name)
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
init_user_and_profile() inv = create_pos_invoice(qty=1, rate=69.5, do_not_save=True)
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 70})
inv.insert()
inv.submit()
inv = create_pos_invoice(qty=1, rate=69.5, do_not_save=True) inv2 = create_pos_invoice(qty=1, rate=59.5, do_not_save=True)
inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 70}) inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60})
inv.insert() inv2.insert()
inv.submit() inv2.submit()
inv2 = create_pos_invoice(qty=1, rate=59.5, do_not_save=True) closing_entry = make_closing_entry_from_opening(opening_entry)
inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 60}) closing_entry.insert()
inv2.insert() closing_entry.submit()
inv2.submit()
consolidate_pos_invoices() inv.load_from_db()
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
inv.load_from_db() self.assertEqual(consolidated_invoice.rounding_adjustment, 1)
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.rounding_adjustment, 1)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
def test_serial_no_case_1(self): def test_serial_no_case_1(self):
""" """
@@ -418,51 +401,46 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
frappe.db.sql("delete from `tabPOS Invoice`") se = make_serialized_item(self)
serial_no = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0]
try: test_user, pos_profile = init_user_and_profile()
se = make_serialized_item(self) opening_entry = create_opening_entry(pos_profile, test_user.name)
serial_no = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)[0]
init_user_and_profile() pos_inv = create_pos_invoice(
item_code="_Test Serialized Item With Series",
serial_no=[serial_no],
qty=1,
rate=100,
do_not_submit=1,
)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_inv.save()
pos_inv.submit()
pos_inv = create_pos_invoice( pos_inv_cn = make_sales_return(pos_inv.name)
item_code="_Test Serialized Item With Series", pos_inv_cn.paid_amount = -100
serial_no=[serial_no], pos_inv_cn.submit()
qty=1,
rate=100,
do_not_submit=1,
)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_inv.save()
pos_inv.submit()
pos_inv_cn = make_sales_return(pos_inv.name) pos_inv2 = create_pos_invoice(
pos_inv_cn.paid_amount = -100 item_code="_Test Serialized Item With Series",
pos_inv_cn.submit() serial_no=[serial_no],
qty=1,
rate=100,
do_not_submit=1,
)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_inv2.save()
pos_inv2.submit()
pos_inv2 = create_pos_invoice( closing_entry = make_closing_entry_from_opening(opening_entry)
item_code="_Test Serialized Item With Series", closing_entry.insert()
serial_no=[serial_no], closing_entry.submit()
qty=1,
rate=100,
do_not_submit=1,
)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
pos_inv2.save()
pos_inv2.submit()
consolidate_pos_invoices() pos_inv.load_from_db()
pos_inv2.load_from_db()
pos_inv.load_from_db() self.assertNotEqual(pos_inv.consolidated_invoice, pos_inv2.consolidated_invoice)
pos_inv2.load_from_db()
self.assertNotEqual(pos_inv.consolidated_invoice, pos_inv2.consolidated_invoice)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
def test_separate_consolidated_invoice_for_different_accounting_dimensions(self): def test_separate_consolidated_invoice_for_different_accounting_dimensions(self):
""" """
@@ -473,48 +451,43 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
""" """
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
frappe.db.sql("delete from `tabPOS Invoice`")
create_cost_center(cost_center_name="_Test POS Cost Center 1", is_group=0) create_cost_center(cost_center_name="_Test POS Cost Center 1", is_group=0)
create_cost_center(cost_center_name="_Test POS Cost Center 2", is_group=0) create_cost_center(cost_center_name="_Test POS Cost Center 2", is_group=0)
try: test_user, pos_profile = init_user_and_profile()
test_user, pos_profile = init_user_and_profile() opening_entry = create_opening_entry(pos_profile, test_user.name)
pos_inv = create_pos_invoice(rate=300, do_not_submit=1) pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300}) pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300})
pos_inv.cost_center = "_Test POS Cost Center 1 - _TC" pos_inv.cost_center = "_Test POS Cost Center 1 - _TC"
pos_inv.save() pos_inv.save()
pos_inv.submit() pos_inv.submit()
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1) pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200}) pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3200})
pos_inv.cost_center = "_Test POS Cost Center 2 - _TC" pos_inv.cost_center = "_Test POS Cost Center 2 - _TC"
pos_inv2.save() pos_inv2.save()
pos_inv2.submit() pos_inv2.submit()
pos_inv3 = create_pos_invoice(rate=2300, do_not_submit=1) pos_inv3 = create_pos_invoice(rate=2300, do_not_submit=1)
pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300}) pos_inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2300})
pos_inv.cost_center = "_Test POS Cost Center 2 - _TC" pos_inv.cost_center = "_Test POS Cost Center 2 - _TC"
pos_inv3.save() pos_inv3.save()
pos_inv3.submit() pos_inv3.submit()
consolidate_pos_invoices() closing_entry = make_closing_entry_from_opening(opening_entry)
closing_entry.insert()
closing_entry.submit()
pos_inv.load_from_db() pos_inv.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice)) self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
pos_inv2.load_from_db() pos_inv2.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv2.consolidated_invoice)) self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv2.consolidated_invoice))
self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice) self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
pos_inv3.load_from_db() pos_inv3.load_from_db()
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice)) self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
self.assertTrue(pos_inv2.consolidated_invoice == pos_inv3.consolidated_invoice) self.assertTrue(pos_inv2.consolidated_invoice == pos_inv3.consolidated_invoice)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")

View File

@@ -5,6 +5,8 @@
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"invoice_type",
"section_break_gyos",
"invoice_fields", "invoice_fields",
"pos_search_fields" "pos_search_fields"
], ],
@@ -12,7 +14,7 @@
{ {
"fieldname": "invoice_fields", "fieldname": "invoice_fields",
"fieldtype": "Table", "fieldtype": "Table",
"label": "POS Field", "label": "POS Additional Fields",
"options": "POS Field" "options": "POS Field"
}, },
{ {
@@ -20,11 +22,23 @@
"fieldtype": "Table", "fieldtype": "Table",
"label": "POS Search Fields", "label": "POS Search Fields",
"options": "POS Search Fields" "options": "POS Search Fields"
},
{
"default": "Sales Invoice",
"description": "The system will create a Sales Invoice or a POS Invoice from the POS interface based on this setting. For high-volume transactions, it is recommended to use POS Invoice.",
"fieldname": "invoice_type",
"fieldtype": "Select",
"label": "Invoice Type Created via POS Screen",
"options": "Sales Invoice\nPOS Invoice"
},
{
"fieldname": "section_break_gyos",
"fieldtype": "Section Break"
} }
], ],
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2024-03-27 13:10:17.083132", "modified": "2025-06-06 11:36:44.885353",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Settings", "name": "POS Settings",
@@ -56,6 +70,7 @@
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"row_format": "Dynamic",
"sort_field": "creation", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [], "states": [],

View File

@@ -21,10 +21,16 @@ class POSSettings(Document):
from erpnext.accounts.doctype.pos_search_fields.pos_search_fields import POSSearchFields from erpnext.accounts.doctype.pos_search_fields.pos_search_fields import POSSearchFields
invoice_fields: DF.Table[POSField] invoice_fields: DF.Table[POSField]
invoice_type: DF.Literal["Sales Invoice", "POS Invoice"]
pos_search_fields: DF.Table[POSSearchFields] pos_search_fields: DF.Table[POSSearchFields]
# end: auto-generated types # end: auto-generated types
def validate(self): def validate(self):
old_doc = self.get_doc_before_save()
if old_doc.invoice_type != self.invoice_type:
self.validate_invoice_type()
self.validate_invoice_fields() self.validate_invoice_fields()
def validate_invoice_fields(self): def validate_invoice_fields(self):
@@ -36,3 +42,15 @@ class POSSettings(Document):
frappe.throw( frappe.throw(
title=_("Duplicate POS Fields"), msg=_("'{0}' has been already added.").format(field) title=_("Duplicate POS Fields"), msg=_("'{0}' has been already added.").format(field)
) )
def validate_invoice_type(self):
pos_opening_entries_count = frappe.db.count(
"POS Opening Entry", filters={"docstatus": 1, "status": "Open"}
)
if pos_opening_entries_count:
frappe.throw(
_("{0} cannot be changed with opened Opening Entries.").format(
frappe.bold(_("Invoice Type"))
),
title=_("Invoice Document Type Selection Error"),
)

View File

@@ -1096,10 +1096,8 @@ class SalesInvoice(SellingController):
if self.is_created_using_pos and not self.pos_profile: if self.is_created_using_pos and not self.pos_profile:
frappe.throw(_("POS Profile is mandatory to mark this invoice as POS Transaction.")) frappe.throw(_("POS Profile is mandatory to mark this invoice as POS Transaction."))
self.is_pos_using_sales_invoice = frappe.get_single_value( self.invoice_type_in_pos = frappe.db.get_single_value("POS Settings", "invoice_type")
"Accounts Settings", "use_sales_invoice_in_pos" if self.invoice_type_in_pos == "POS Invoice" and not self.is_return:
)
if not self.is_pos_using_sales_invoice and not self.is_return:
frappe.throw(_("Transactions using Sales Invoice in POS are disabled.")) frappe.throw(_("Transactions using Sales Invoice in POS are disabled."))
def validate_full_payment(self): def validate_full_payment(self):

View File

@@ -4425,7 +4425,7 @@ class TestSalesInvoice(ERPNextTestSuite):
# Deleting all opening entry # Deleting all opening entry
frappe.db.sql("delete from `tabPOS Opening Entry`") frappe.db.sql("delete from `tabPOS Opening Entry`")
with self.change_settings("Accounts Settings", {"use_sales_invoice_in_pos": 0}): with self.change_settings("POS Settings", {"invoice_type": "POS Invoice"}):
pos_profile = make_pos_profile() pos_profile = make_pos_profile()
pos_profile.payments = [] pos_profile.payments = []
@@ -4495,6 +4495,14 @@ def create_sales_invoice(**args):
si.naming_series = args.naming_series or "T-SINV-" si.naming_series = args.naming_series or "T-SINV-"
si.cost_center = args.parent_cost_center si.cost_center = args.parent_cost_center
si.is_internal_customer = args.is_internal_customer or 0 si.is_internal_customer = args.is_internal_customer or 0
if args.is_created_using_pos:
si.is_pos = 1
si.is_created_using_pos = 1
pos_profile = None
if not args.pos_profile:
pos_profile = make_pos_profile()
pos_profile.save()
si.pos_profile = args.pos_profile or pos_profile.name
bundle_id = None bundle_id = None
if si.update_stock and (args.get("batch_no") or args.get("serial_no")): if si.update_stock and (args.get("batch_no") or args.get("serial_no")):

View File

@@ -420,3 +420,4 @@ erpnext.patches.v15_0.remove_agriculture_roles
erpnext.patches.v14_0.update_full_name_in_contract erpnext.patches.v14_0.update_full_name_in_contract
erpnext.patches.v15_0.drop_sle_indexes erpnext.patches.v15_0.drop_sle_indexes
execute:frappe.db.set_single_value("Accounts Settings", "confirm_before_resetting_posting_date", 1) execute:frappe.db.set_single_value("Accounts Settings", "confirm_before_resetting_posting_date", 1)
erpnext.patches.v15_0.rename_pos_closing_entry_fields

View File

@@ -0,0 +1,6 @@
from frappe.model.utils.rename_field import rename_field
def execute():
rename_field("POS Closing Entry", "pos_transactions", "pos_invoices")
rename_field("POS Closing Entry", "sales_invoice_transactions", "sales_invoices")

View File

@@ -139,10 +139,7 @@ erpnext.PointOfSale.Controller = class {
this.allow_negative_stock = flt(message.allow_negative_stock) || false; this.allow_negative_stock = flt(message.allow_negative_stock) || false;
}); });
const use_sales_invoice_in_pos = await frappe.db.get_single_value( const invoice_doctype = await frappe.db.get_single_value("POS Settings", "invoice_type");
"Accounts Settings",
"use_sales_invoice_in_pos"
);
frappe.call({ frappe.call({
method: "erpnext.selling.page.point_of_sale.point_of_sale.get_pos_profile_data", method: "erpnext.selling.page.point_of_sale.point_of_sale.get_pos_profile_data",
@@ -151,7 +148,7 @@ erpnext.PointOfSale.Controller = class {
const profile = res.message; const profile = res.message;
Object.assign(this.settings, profile); Object.assign(this.settings, profile);
this.settings.customer_groups = profile.customer_groups.map((group) => group.name); this.settings.customer_groups = profile.customer_groups.map((group) => group.name);
this.settings.frm_doctype = use_sales_invoice_in_pos ? "Sales Invoice" : "POS Invoice"; this.settings.frm_doctype = invoice_doctype;
this.make_app(); this.make_app();
}, },
}); });