mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-26 00:14:50 +00:00
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:
@@ -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",
|
||||||
|
|||||||
@@ -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"),
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,15 +27,16 @@
|
|||||||
],
|
],
|
||||||
"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": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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`")
|
|
||||||
|
|||||||
@@ -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,8 +70,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
)
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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")):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
6
erpnext/patches/v15_0/rename_pos_closing_entry_fields.py
Normal file
6
erpnext/patches/v15_0/rename_pos_closing_entry_fields.py
Normal 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")
|
||||||
@@ -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();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user