mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 11:19:09 +00:00
Merge branch 'develop' into st31369
This commit is contained in:
@@ -123,3 +123,20 @@ class TestGLEntry(IntegrationTestCase):
|
|||||||
str(e),
|
str(e),
|
||||||
"Party Type and Party can only be set for Receivable / Payable account_Test Account Cost for Goods Sold - _TC",
|
"Party Type and Party can only be set for Receivable / Payable account_Test Account Cost for Goods Sold - _TC",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_validate_account_party_type_shareholder(self):
|
||||||
|
jv = make_journal_entry(
|
||||||
|
"Opening Balance Equity - _TC",
|
||||||
|
"Cash - _TC",
|
||||||
|
100,
|
||||||
|
"_Test Cost Center - _TC",
|
||||||
|
save=False,
|
||||||
|
submit=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in jv.accounts:
|
||||||
|
row.party_type = "Shareholder"
|
||||||
|
break
|
||||||
|
|
||||||
|
jv.save().submit()
|
||||||
|
self.assertEqual(1, jv.docstatus)
|
||||||
|
|||||||
@@ -374,7 +374,6 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
frm.set_df_property("total_allocated_amount", "options", currency_field);
|
frm.set_df_property("total_allocated_amount", "options", currency_field);
|
||||||
frm.set_df_property("unallocated_amount", "options", currency_field);
|
frm.set_df_property("unallocated_amount", "options", currency_field);
|
||||||
frm.set_df_property("total_taxes_and_charges", "options", currency_field);
|
frm.set_df_property("total_taxes_and_charges", "options", currency_field);
|
||||||
frm.set_df_property("party_balance", "options", currency_field);
|
|
||||||
|
|
||||||
frm.set_currency_labels(
|
frm.set_currency_labels(
|
||||||
["total_amount", "outstanding_amount", "allocated_amount"],
|
["total_amount", "outstanding_amount", "allocated_amount"],
|
||||||
@@ -422,15 +421,7 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
|
|
||||||
if (frm.doc.payment_type == "Internal Transfer") {
|
if (frm.doc.payment_type == "Internal Transfer") {
|
||||||
$.each(
|
$.each(
|
||||||
[
|
["party", "party_type", "paid_from", "paid_to", "references", "total_allocated_amount"],
|
||||||
"party",
|
|
||||||
"party_type",
|
|
||||||
"party_balance",
|
|
||||||
"paid_from",
|
|
||||||
"paid_to",
|
|
||||||
"references",
|
|
||||||
"total_allocated_amount",
|
|
||||||
],
|
|
||||||
function (i, field) {
|
function (i, field) {
|
||||||
frm.set_value(field, null);
|
frm.set_value(field, null);
|
||||||
}
|
}
|
||||||
@@ -478,13 +469,10 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
$.each(
|
$.each(
|
||||||
[
|
[
|
||||||
"party",
|
"party",
|
||||||
"party_balance",
|
|
||||||
"paid_from",
|
"paid_from",
|
||||||
"paid_to",
|
"paid_to",
|
||||||
"paid_from_account_currency",
|
"paid_from_account_currency",
|
||||||
"paid_from_account_balance",
|
|
||||||
"paid_to_account_currency",
|
"paid_to_account_currency",
|
||||||
"paid_to_account_balance",
|
|
||||||
"references",
|
"references",
|
||||||
"total_allocated_amount",
|
"total_allocated_amount",
|
||||||
],
|
],
|
||||||
@@ -529,17 +517,14 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
"paid_from_account_currency",
|
"paid_from_account_currency",
|
||||||
r.message.party_account_currency
|
r.message.party_account_currency
|
||||||
);
|
);
|
||||||
frm.set_value("paid_from_account_balance", r.message.account_balance);
|
|
||||||
} else if (frm.doc.payment_type == "Pay") {
|
} else if (frm.doc.payment_type == "Pay") {
|
||||||
frm.set_value("paid_to", r.message.party_account);
|
frm.set_value("paid_to", r.message.party_account);
|
||||||
frm.set_value(
|
frm.set_value(
|
||||||
"paid_to_account_currency",
|
"paid_to_account_currency",
|
||||||
r.message.party_account_currency
|
r.message.party_account_currency
|
||||||
);
|
);
|
||||||
frm.set_value("paid_to_account_balance", r.message.account_balance);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
() => frm.set_value("party_balance", r.message.party_balance),
|
|
||||||
() => frm.set_value("party_name", r.message.party_name),
|
() => frm.set_value("party_name", r.message.party_name),
|
||||||
() => frm.clear_table("references"),
|
() => frm.clear_table("references"),
|
||||||
() => frm.events.hide_unhide_fields(frm),
|
() => frm.events.hide_unhide_fields(frm),
|
||||||
@@ -591,7 +576,6 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
frm,
|
frm,
|
||||||
frm.doc.paid_from,
|
frm.doc.paid_from,
|
||||||
"paid_from_account_currency",
|
"paid_from_account_currency",
|
||||||
"paid_from_account_balance",
|
|
||||||
function (frm) {
|
function (frm) {
|
||||||
if (frm.doc.payment_type == "Pay") {
|
if (frm.doc.payment_type == "Pay") {
|
||||||
frm.events.paid_amount(frm);
|
frm.events.paid_amount(frm);
|
||||||
@@ -607,7 +591,6 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
frm,
|
frm,
|
||||||
frm.doc.paid_to,
|
frm.doc.paid_to,
|
||||||
"paid_to_account_currency",
|
"paid_to_account_currency",
|
||||||
"paid_to_account_balance",
|
|
||||||
function (frm) {
|
function (frm) {
|
||||||
if (frm.doc.payment_type == "Receive") {
|
if (frm.doc.payment_type == "Receive") {
|
||||||
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
|
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
|
||||||
@@ -623,13 +606,7 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
set_account_currency_and_balance: function (
|
set_account_currency_and_balance: function (frm, account, currency_field, callback_function) {
|
||||||
frm,
|
|
||||||
account,
|
|
||||||
currency_field,
|
|
||||||
balance_field,
|
|
||||||
callback_function
|
|
||||||
) {
|
|
||||||
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||||
if (frm.doc.posting_date && account) {
|
if (frm.doc.posting_date && account) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
@@ -644,8 +621,6 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
frappe.run_serially([
|
frappe.run_serially([
|
||||||
() => frm.set_value(currency_field, r.message["account_currency"]),
|
() => frm.set_value(currency_field, r.message["account_currency"]),
|
||||||
() => {
|
() => {
|
||||||
frm.set_value(balance_field, r.message["account_balance"]);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
frm.doc.payment_type == "Receive" &&
|
frm.doc.payment_type == "Receive" &&
|
||||||
currency_field == "paid_to_account_currency"
|
currency_field == "paid_to_account_currency"
|
||||||
@@ -1684,37 +1659,6 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
return current_tax_amount;
|
return current_tax_amount;
|
||||||
},
|
},
|
||||||
|
|
||||||
cost_center: function (frm) {
|
|
||||||
if (frm.doc.posting_date && (frm.doc.paid_from || frm.doc.paid_to)) {
|
|
||||||
return frappe.call({
|
|
||||||
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_party_and_account_balance",
|
|
||||||
args: {
|
|
||||||
company: frm.doc.company,
|
|
||||||
date: frm.doc.posting_date,
|
|
||||||
paid_from: frm.doc.paid_from,
|
|
||||||
paid_to: frm.doc.paid_to,
|
|
||||||
ptype: frm.doc.party_type,
|
|
||||||
pty: frm.doc.party,
|
|
||||||
cost_center: frm.doc.cost_center,
|
|
||||||
},
|
|
||||||
callback: function (r, rt) {
|
|
||||||
if (r.message) {
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
frm.set_value(
|
|
||||||
"paid_from_account_balance",
|
|
||||||
r.message.paid_from_account_balance
|
|
||||||
);
|
|
||||||
frm.set_value("paid_to_account_balance", r.message.paid_to_account_balance);
|
|
||||||
frm.set_value("party_balance", r.message.party_balance);
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
after_save: function (frm) {
|
after_save: function (frm) {
|
||||||
const { matched_payment_requests } = frappe.last_response;
|
const { matched_payment_requests } = frappe.last_response;
|
||||||
if (!matched_payment_requests) return;
|
if (!matched_payment_requests) return;
|
||||||
|
|||||||
@@ -28,16 +28,13 @@
|
|||||||
"contact_person",
|
"contact_person",
|
||||||
"contact_email",
|
"contact_email",
|
||||||
"payment_accounts_section",
|
"payment_accounts_section",
|
||||||
"party_balance",
|
|
||||||
"paid_from",
|
"paid_from",
|
||||||
"paid_from_account_type",
|
"paid_from_account_type",
|
||||||
"paid_from_account_currency",
|
"paid_from_account_currency",
|
||||||
"paid_from_account_balance",
|
|
||||||
"column_break_18",
|
"column_break_18",
|
||||||
"paid_to",
|
"paid_to",
|
||||||
"paid_to_account_type",
|
"paid_to_account_type",
|
||||||
"paid_to_account_currency",
|
"paid_to_account_currency",
|
||||||
"paid_to_account_balance",
|
|
||||||
"payment_amounts_section",
|
"payment_amounts_section",
|
||||||
"paid_amount",
|
"paid_amount",
|
||||||
"paid_amount_after_tax",
|
"paid_amount_after_tax",
|
||||||
@@ -223,16 +220,6 @@
|
|||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Accounts"
|
"label": "Accounts"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"allow_on_submit": 1,
|
|
||||||
"depends_on": "party",
|
|
||||||
"fieldname": "party_balance",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"label": "Party Balance",
|
|
||||||
"no_copy": 1,
|
|
||||||
"print_hide": 1,
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"bold": 1,
|
"bold": 1,
|
||||||
"depends_on": "eval:(in_list([\"Internal Transfer\", \"Pay\"], doc.payment_type) || doc.party)",
|
"depends_on": "eval:(in_list([\"Internal Transfer\", \"Pay\"], doc.payment_type) || doc.party)",
|
||||||
@@ -254,16 +241,6 @@
|
|||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"allow_on_submit": 1,
|
|
||||||
"depends_on": "paid_from",
|
|
||||||
"fieldname": "paid_from_account_balance",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"label": "Account Balance (From)",
|
|
||||||
"options": "paid_from_account_currency",
|
|
||||||
"print_hide": 1,
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_18",
|
"fieldname": "column_break_18",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
@@ -288,16 +265,6 @@
|
|||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"allow_on_submit": 1,
|
|
||||||
"depends_on": "paid_to",
|
|
||||||
"fieldname": "paid_to_account_balance",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"label": "Account Balance (To)",
|
|
||||||
"options": "paid_to_account_currency",
|
|
||||||
"print_hide": 1,
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"depends_on": "eval:(doc.paid_to && doc.paid_from)",
|
"depends_on": "eval:(doc.paid_to && doc.paid_from)",
|
||||||
"fieldname": "payment_amounts_section",
|
"fieldname": "payment_amounts_section",
|
||||||
@@ -810,7 +777,7 @@
|
|||||||
"table_fieldname": "payment_entries"
|
"table_fieldname": "payment_entries"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2025-01-31 17:27:28.555246",
|
"modified": "2025-01-31 11:24:58.076393",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry",
|
"name": "Payment Entry",
|
||||||
|
|||||||
@@ -108,15 +108,12 @@ class PaymentEntry(AccountsController):
|
|||||||
paid_amount: DF.Currency
|
paid_amount: DF.Currency
|
||||||
paid_amount_after_tax: DF.Currency
|
paid_amount_after_tax: DF.Currency
|
||||||
paid_from: DF.Link
|
paid_from: DF.Link
|
||||||
paid_from_account_balance: DF.Currency
|
|
||||||
paid_from_account_currency: DF.Link
|
paid_from_account_currency: DF.Link
|
||||||
paid_from_account_type: DF.Data | None
|
paid_from_account_type: DF.Data | None
|
||||||
paid_to: DF.Link
|
paid_to: DF.Link
|
||||||
paid_to_account_balance: DF.Currency
|
|
||||||
paid_to_account_currency: DF.Link
|
paid_to_account_currency: DF.Link
|
||||||
paid_to_account_type: DF.Data | None
|
paid_to_account_type: DF.Data | None
|
||||||
party: DF.DynamicLink | None
|
party: DF.DynamicLink | None
|
||||||
party_balance: DF.Currency
|
|
||||||
party_bank_account: DF.Link | None
|
party_bank_account: DF.Link | None
|
||||||
party_name: DF.Data | None
|
party_name: DF.Data | None
|
||||||
party_type: DF.Link | None
|
party_type: DF.Link | None
|
||||||
@@ -506,7 +503,6 @@ class PaymentEntry(AccountsController):
|
|||||||
if self.payment_type == "Internal Transfer":
|
if self.payment_type == "Internal Transfer":
|
||||||
for field in (
|
for field in (
|
||||||
"party",
|
"party",
|
||||||
"party_balance",
|
|
||||||
"total_allocated_amount",
|
"total_allocated_amount",
|
||||||
"base_total_allocated_amount",
|
"base_total_allocated_amount",
|
||||||
"unallocated_amount",
|
"unallocated_amount",
|
||||||
@@ -534,25 +530,19 @@ class PaymentEntry(AccountsController):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
complete_contact_details(self)
|
complete_contact_details(self)
|
||||||
if not self.party_balance:
|
|
||||||
self.party_balance = get_balance_on(
|
|
||||||
party_type=self.party_type, party=self.party, date=self.posting_date, company=self.company
|
|
||||||
)
|
|
||||||
|
|
||||||
if not self.party_account:
|
if not self.party_account:
|
||||||
party_account = get_party_account(self.party_type, self.party, self.company)
|
party_account = get_party_account(self.party_type, self.party, self.company)
|
||||||
self.set(self.party_account_field, party_account)
|
self.set(self.party_account_field, party_account)
|
||||||
self.party_account = party_account
|
self.party_account = party_account
|
||||||
|
|
||||||
if self.paid_from and not self.paid_from_account_currency and not self.paid_from_account_balance:
|
if self.paid_from and not self.paid_from_account_currency:
|
||||||
acc = get_account_details(self.paid_from, self.posting_date, self.cost_center)
|
acc = get_account_details(self.paid_from, self.posting_date, self.cost_center)
|
||||||
self.paid_from_account_currency = acc.account_currency
|
self.paid_from_account_currency = acc.account_currency
|
||||||
self.paid_from_account_balance = acc.account_balance
|
|
||||||
|
|
||||||
if self.paid_to and not self.paid_to_account_currency and not self.paid_to_account_balance:
|
if self.paid_to and not self.paid_to_account_currency:
|
||||||
acc = get_account_details(self.paid_to, self.posting_date, self.cost_center)
|
acc = get_account_details(self.paid_to, self.posting_date, self.cost_center)
|
||||||
self.paid_to_account_currency = acc.account_currency
|
self.paid_to_account_currency = acc.account_currency
|
||||||
self.paid_to_account_balance = acc.account_balance
|
|
||||||
|
|
||||||
self.party_account_currency = (
|
self.party_account_currency = (
|
||||||
self.paid_from_account_currency
|
self.paid_from_account_currency
|
||||||
@@ -2721,9 +2711,7 @@ def get_party_details(company, party_type, party, date, cost_center=None):
|
|||||||
account_balance = get_balance_on(party_account, date, cost_center=cost_center)
|
account_balance = get_balance_on(party_account, date, cost_center=cost_center)
|
||||||
_party_name = "title" if party_type == "Shareholder" else party_type.lower() + "_name"
|
_party_name = "title" if party_type == "Shareholder" else party_type.lower() + "_name"
|
||||||
party_name = frappe.db.get_value(party_type, party, _party_name)
|
party_name = frappe.db.get_value(party_type, party, _party_name)
|
||||||
party_balance = get_balance_on(
|
|
||||||
party_type=party_type, party=party, company=company, cost_center=cost_center
|
|
||||||
)
|
|
||||||
if party_type in ["Customer", "Supplier"]:
|
if party_type in ["Customer", "Supplier"]:
|
||||||
party_bank_account = get_party_bank_account(party_type, party)
|
party_bank_account = get_party_bank_account(party_type, party)
|
||||||
bank_account = get_default_company_bank_account(company, party_type, party)
|
bank_account = get_default_company_bank_account(company, party_type, party)
|
||||||
@@ -2732,7 +2720,6 @@ def get_party_details(company, party_type, party, date, cost_center=None):
|
|||||||
"party_account": party_account,
|
"party_account": party_account,
|
||||||
"party_name": party_name,
|
"party_name": party_name,
|
||||||
"party_account_currency": account_currency,
|
"party_account_currency": account_currency,
|
||||||
"party_balance": party_balance,
|
|
||||||
"account_balance": account_balance,
|
"account_balance": account_balance,
|
||||||
"party_bank_account": party_bank_account,
|
"party_bank_account": party_bank_account,
|
||||||
"bank_account": bank_account,
|
"bank_account": bank_account,
|
||||||
@@ -3558,19 +3545,6 @@ def get_paid_amount(dt, dn, party_type, party, account, due_date):
|
|||||||
return paid_amount[0][0] if paid_amount else 0
|
return paid_amount[0][0] if paid_amount else 0
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_party_and_account_balance(
|
|
||||||
company, date, paid_from=None, paid_to=None, ptype=None, pty=None, cost_center=None
|
|
||||||
):
|
|
||||||
return frappe._dict(
|
|
||||||
{
|
|
||||||
"party_balance": get_balance_on(party_type=ptype, party=pty, cost_center=cost_center),
|
|
||||||
"paid_from_account_balance": get_balance_on(paid_from, date, cost_center=cost_center),
|
|
||||||
"paid_to_account_balance": get_balance_on(paid_to, date=date, cost_center=cost_center),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_payment_order(source_name, target_doc=None):
|
def make_payment_order(source_name, target_doc=None):
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
|
|||||||
@@ -781,7 +781,10 @@ def get_existing_paid_amount(doctype, name):
|
|||||||
frappe.qb.from_(PL)
|
frappe.qb.from_(PL)
|
||||||
.left_join(PER)
|
.left_join(PER)
|
||||||
.on(
|
.on(
|
||||||
(PER.reference_doctype == PL.against_voucher_type) & (PER.reference_name == PL.against_voucher_no)
|
(PL.against_voucher_type == PER.reference_doctype)
|
||||||
|
& (PL.against_voucher_no == PER.reference_name)
|
||||||
|
& (PL.voucher_type == PER.parenttype)
|
||||||
|
& (PL.voucher_no == PER.parent)
|
||||||
)
|
)
|
||||||
.select(Abs(Sum(PL.amount)).as_("total_paid_amount"))
|
.select(Abs(Sum(PL.amount)).as_("total_paid_amount"))
|
||||||
.where(PL.against_voucher_type.eq(doctype))
|
.where(PL.against_voucher_type.eq(doctype))
|
||||||
|
|||||||
@@ -709,6 +709,45 @@ class TestPaymentRequest(IntegrationTestCase):
|
|||||||
|
|
||||||
self.assertEqual(pr.grand_total, si.outstanding_amount)
|
self.assertEqual(pr.grand_total, si.outstanding_amount)
|
||||||
|
|
||||||
|
def test_partial_paid_invoice_with_more_payment_entry(self):
|
||||||
|
pi = make_purchase_invoice(currency="INR", qty=1, rate=500)
|
||||||
|
pi.submit()
|
||||||
|
pi_1 = make_purchase_invoice(currency="INR", qty=1, rate=300)
|
||||||
|
pi_1.submit()
|
||||||
|
|
||||||
|
pr = make_payment_request(dt="Purchase Invoice", dn=pi.name, mute_email=1, submit_doc=0, return_doc=1)
|
||||||
|
pr.grand_total = 200
|
||||||
|
pr.submit()
|
||||||
|
pr.create_payment_entry()
|
||||||
|
pr_1 = make_payment_request(
|
||||||
|
dt="Purchase Invoice", dn=pi.name, mute_email=1, submit_doc=0, return_doc=1
|
||||||
|
)
|
||||||
|
pr_1.grand_total = 200
|
||||||
|
pr_1.submit()
|
||||||
|
pr_1.create_payment_entry()
|
||||||
|
|
||||||
|
pe = get_payment_entry(dt="Purchase Invoice", dn=pi.name)
|
||||||
|
pe.paid_amount = 200
|
||||||
|
pe.references[0].reference_doctype = pi.doctype
|
||||||
|
pe.references[0].reference_name = pi.name
|
||||||
|
pe.references[0].grand_total = pi.grand_total
|
||||||
|
pe.references[0].outstanding_amount = pi.outstanding_amount
|
||||||
|
pe.references[0].allocated_amount = 100
|
||||||
|
pe.append(
|
||||||
|
"references",
|
||||||
|
{
|
||||||
|
"reference_doctype": pi_1.doctype,
|
||||||
|
"reference_name": pi_1.name,
|
||||||
|
"grand_total": pi_1.grand_total,
|
||||||
|
"outstanding_amount": pi_1.outstanding_amount,
|
||||||
|
"allocated_amount": 100,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
pr_2 = make_payment_request(dt="Purchase Invoice", dn=pi.name, mute_email=1)
|
||||||
|
pi.load_from_db()
|
||||||
|
self.assertEqual(pr_2.grand_total, pi.outstanding_amount)
|
||||||
|
|
||||||
|
|
||||||
def test_partial_paid_invoice_with_submitted_payment_entry(self):
|
def test_partial_paid_invoice_with_submitted_payment_entry(self):
|
||||||
pi = make_purchase_invoice(currency="INR", qty=1, rate=5000)
|
pi = make_purchase_invoice(currency="INR", qty=1, rate=5000)
|
||||||
|
|||||||
@@ -39,10 +39,12 @@ class TestPOSClosingEntry(IntegrationTestCase):
|
|||||||
|
|
||||||
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
|
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
|
||||||
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
|
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
|
||||||
|
pos_inv1.save()
|
||||||
pos_inv1.submit()
|
pos_inv1.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_inv2.save()
|
||||||
pos_inv2.submit()
|
pos_inv2.submit()
|
||||||
|
|
||||||
pcv_doc = make_closing_entry_from_opening(opening_entry)
|
pcv_doc = make_closing_entry_from_opening(opening_entry)
|
||||||
@@ -68,6 +70,7 @@ class TestPOSClosingEntry(IntegrationTestCase):
|
|||||||
|
|
||||||
pos_inv = create_pos_invoice(rate=3500, do_not_submit=1, item_name="Test Item", without_item_code=1)
|
pos_inv = create_pos_invoice(rate=3500, do_not_submit=1, item_name="Test Item", without_item_code=1)
|
||||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
|
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
|
||||||
|
pos_inv.save()
|
||||||
pos_inv.submit()
|
pos_inv.submit()
|
||||||
|
|
||||||
pcv_doc = make_closing_entry_from_opening(opening_entry)
|
pcv_doc = make_closing_entry_from_opening(opening_entry)
|
||||||
@@ -86,10 +89,12 @@ class TestPOSClosingEntry(IntegrationTestCase):
|
|||||||
|
|
||||||
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
|
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
|
||||||
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
|
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
|
||||||
|
pos_inv1.save()
|
||||||
pos_inv1.submit()
|
pos_inv1.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_inv2.save()
|
||||||
pos_inv2.submit()
|
pos_inv2.submit()
|
||||||
|
|
||||||
# make return entry of pos_inv2
|
# make return entry of pos_inv2
|
||||||
@@ -111,10 +116,12 @@ class TestPOSClosingEntry(IntegrationTestCase):
|
|||||||
|
|
||||||
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
|
pos_inv1 = create_pos_invoice(rate=3500, do_not_submit=1)
|
||||||
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
|
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
|
||||||
|
pos_inv1.save()
|
||||||
pos_inv1.submit()
|
pos_inv1.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_inv2.save()
|
||||||
pos_inv2.submit()
|
pos_inv2.submit()
|
||||||
|
|
||||||
pcv_doc = make_closing_entry_from_opening(opening_entry)
|
pcv_doc = make_closing_entry_from_opening(opening_entry)
|
||||||
@@ -165,6 +172,7 @@ class TestPOSClosingEntry(IntegrationTestCase):
|
|||||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||||
pos_inv1 = create_pos_invoice(rate=350, do_not_submit=1, pos_profile=pos_profile.name)
|
pos_inv1 = create_pos_invoice(rate=350, do_not_submit=1, pos_profile=pos_profile.name)
|
||||||
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
|
pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
|
||||||
|
pos_inv1.save()
|
||||||
pos_inv1.submit()
|
pos_inv1.submit()
|
||||||
|
|
||||||
# if in between a mandatory accounting dimension is added to the POS Profile then
|
# if in between a mandatory accounting dimension is added to the POS Profile then
|
||||||
@@ -226,6 +234,7 @@ class TestPOSClosingEntry(IntegrationTestCase):
|
|||||||
do_not_submit=True,
|
do_not_submit=True,
|
||||||
)
|
)
|
||||||
pos_inv.payments[0].amount = pos_inv.grand_total
|
pos_inv.payments[0].amount = pos_inv.grand_total
|
||||||
|
pos_inv.save()
|
||||||
pos_inv.submit()
|
pos_inv.submit()
|
||||||
pos_inv2 = create_pos_invoice(
|
pos_inv2 = create_pos_invoice(
|
||||||
item_code=item_code,
|
item_code=item_code,
|
||||||
@@ -236,6 +245,7 @@ class TestPOSClosingEntry(IntegrationTestCase):
|
|||||||
do_not_submit=True,
|
do_not_submit=True,
|
||||||
)
|
)
|
||||||
pos_inv2.payments[0].amount = pos_inv2.grand_total
|
pos_inv2.payments[0].amount = pos_inv2.grand_total
|
||||||
|
pos_inv2.save()
|
||||||
pos_inv2.submit()
|
pos_inv2.submit()
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ from erpnext.controllers.queries import item_query as _item_query
|
|||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
|
|
||||||
|
class PartialPaymentValidationError(frappe.ValidationError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class POSInvoice(SalesInvoice):
|
class POSInvoice(SalesInvoice):
|
||||||
# begin: auto-generated types
|
# begin: auto-generated types
|
||||||
# This code is auto-generated. Do not modify anything in this block.
|
# This code is auto-generated. Do not modify anything in this block.
|
||||||
@@ -210,6 +214,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
self.validate_payment_amount()
|
self.validate_payment_amount()
|
||||||
self.validate_loyalty_transaction()
|
self.validate_loyalty_transaction()
|
||||||
self.validate_company_with_pos_company()
|
self.validate_company_with_pos_company()
|
||||||
|
self.validate_full_payment()
|
||||||
if self.coupon_code:
|
if self.coupon_code:
|
||||||
from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
|
from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
|
||||||
|
|
||||||
@@ -484,6 +489,20 @@ class POSInvoice(SalesInvoice):
|
|||||||
if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points:
|
if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points:
|
||||||
validate_loyalty_points(self, self.loyalty_points)
|
validate_loyalty_points(self, self.loyalty_points)
|
||||||
|
|
||||||
|
def validate_full_payment(self):
|
||||||
|
invoice_total = flt(self.rounded_total) or flt(self.grand_total)
|
||||||
|
|
||||||
|
if self.docstatus == 1:
|
||||||
|
if self.is_return and self.paid_amount != invoice_total:
|
||||||
|
frappe.throw(
|
||||||
|
msg=_("Partial Payment in POS Invoice is not allowed."), exc=PartialPaymentValidationError
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.paid_amount < invoice_total:
|
||||||
|
frappe.throw(
|
||||||
|
msg=_("Partial Payment in POS Invoice is not allowed."), exc=PartialPaymentValidationError
|
||||||
|
)
|
||||||
|
|
||||||
def set_status(self, update=False, status=None, update_modified=True):
|
def set_status(self, update=False, status=None, update_modified=True):
|
||||||
if self.is_new():
|
if self.is_new():
|
||||||
if self.get("amended_from"):
|
if self.get("amended_from"):
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import frappe
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.tests import IntegrationTestCase
|
from frappe.tests import IntegrationTestCase
|
||||||
|
|
||||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
from erpnext.accounts.doctype.pos_invoice.pos_invoice import PartialPaymentValidationError, make_sales_return
|
||||||
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.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
@@ -317,7 +317,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
pos.append(
|
pos.append(
|
||||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
|
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 2000, "default": 1}
|
||||||
)
|
)
|
||||||
|
|
||||||
pos.insert()
|
pos.insert()
|
||||||
@@ -328,6 +328,11 @@ class TestPOSInvoice(IntegrationTestCase):
|
|||||||
|
|
||||||
# partial return 1
|
# partial return 1
|
||||||
pos_return1.get("items")[0].qty = -1
|
pos_return1.get("items")[0].qty = -1
|
||||||
|
pos_return1.set("payments", [])
|
||||||
|
pos_return1.append(
|
||||||
|
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -1000, "default": 1}
|
||||||
|
)
|
||||||
|
pos_return1.paid_amount = -1000
|
||||||
pos_return1.submit()
|
pos_return1.submit()
|
||||||
pos_return1.reload()
|
pos_return1.reload()
|
||||||
|
|
||||||
@@ -342,6 +347,11 @@ class TestPOSInvoice(IntegrationTestCase):
|
|||||||
|
|
||||||
# partial return 2
|
# partial return 2
|
||||||
pos_return2 = make_sales_return(pos.name)
|
pos_return2 = make_sales_return(pos.name)
|
||||||
|
pos_return2.set("payments", [])
|
||||||
|
pos_return2.append(
|
||||||
|
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": -1000, "default": 1}
|
||||||
|
)
|
||||||
|
pos_return2.paid_amount = -1000
|
||||||
pos_return2.submit()
|
pos_return2.submit()
|
||||||
|
|
||||||
self.assertEqual(pos_return2.get("items")[0].qty, -1)
|
self.assertEqual(pos_return2.get("items")[0].qty, -1)
|
||||||
@@ -377,6 +387,15 @@ class TestPOSInvoice(IntegrationTestCase):
|
|||||||
inv.payments = []
|
inv.payments = []
|
||||||
self.assertRaises(frappe.ValidationError, inv.insert)
|
self.assertRaises(frappe.ValidationError, inv.insert)
|
||||||
|
|
||||||
|
def test_partial_payment(self):
|
||||||
|
pos_inv = create_pos_invoice(rate=10000, do_not_save=1)
|
||||||
|
pos_inv.append(
|
||||||
|
"payments",
|
||||||
|
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 9000},
|
||||||
|
)
|
||||||
|
pos_inv.insert()
|
||||||
|
self.assertRaises(PartialPaymentValidationError, pos_inv.submit)
|
||||||
|
|
||||||
def test_serialized_item_transaction(self):
|
def test_serialized_item_transaction(self):
|
||||||
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
|
||||||
|
|
||||||
@@ -589,7 +608,13 @@ class TestPOSInvoice(IntegrationTestCase):
|
|||||||
"Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty"
|
"Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty"
|
||||||
)
|
)
|
||||||
|
|
||||||
inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000)
|
inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000, do_not_save=1)
|
||||||
|
inv.append(
|
||||||
|
"payments",
|
||||||
|
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 10000},
|
||||||
|
)
|
||||||
|
inv.insert()
|
||||||
|
inv.submit()
|
||||||
|
|
||||||
lpe = frappe.get_doc(
|
lpe = frappe.get_doc(
|
||||||
"Loyalty Point Entry",
|
"Loyalty Point Entry",
|
||||||
@@ -615,7 +640,13 @@ class TestPOSInvoice(IntegrationTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# add 10 loyalty points
|
# add 10 loyalty points
|
||||||
create_pos_invoice(customer="Test Loyalty Customer", rate=10000)
|
pos_inv = create_pos_invoice(customer="Test Loyalty Customer", rate=10000, do_not_save=1)
|
||||||
|
pos_inv.append(
|
||||||
|
"payments",
|
||||||
|
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 10000},
|
||||||
|
)
|
||||||
|
pos_inv.paid_amount = 10000
|
||||||
|
pos_inv.submit()
|
||||||
|
|
||||||
before_lp_details = get_loyalty_program_details_with_points(
|
before_lp_details = get_loyalty_program_details_with_points(
|
||||||
"Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty"
|
"Test Loyalty Customer", company="_Test Company", loyalty_program="Test Single Loyalty"
|
||||||
@@ -649,10 +680,12 @@ class TestPOSInvoice(IntegrationTestCase):
|
|||||||
test_user, pos_profile = init_user_and_profile()
|
test_user, pos_profile = init_user_and_profile()
|
||||||
pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1)
|
pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1)
|
||||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 270})
|
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 270})
|
||||||
|
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_inv2.save()
|
||||||
pos_inv2.submit()
|
pos_inv2.submit()
|
||||||
|
|
||||||
consolidate_pos_invoices()
|
consolidate_pos_invoices()
|
||||||
@@ -684,6 +717,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
|||||||
"included_in_print_rate": 1,
|
"included_in_print_rate": 1,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
pos_inv.save()
|
||||||
pos_inv.submit()
|
pos_inv.submit()
|
||||||
|
|
||||||
pos_inv2 = create_pos_invoice(rate=300, qty=2, do_not_submit=1)
|
pos_inv2 = create_pos_invoice(rate=300, qty=2, do_not_submit=1)
|
||||||
@@ -700,6 +734,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
|||||||
"included_in_print_rate": 1,
|
"included_in_print_rate": 1,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
pos_inv2.save()
|
||||||
pos_inv2.submit()
|
pos_inv2.submit()
|
||||||
|
|
||||||
consolidate_pos_invoices()
|
consolidate_pos_invoices()
|
||||||
@@ -752,6 +787,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
|||||||
"included_in_print_rate": 1,
|
"included_in_print_rate": 1,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
pos_inv2.save()
|
||||||
pos_inv2.submit()
|
pos_inv2.submit()
|
||||||
|
|
||||||
consolidate_pos_invoices()
|
consolidate_pos_invoices()
|
||||||
@@ -782,7 +818,10 @@ class TestPOSInvoice(IntegrationTestCase):
|
|||||||
|
|
||||||
# POS Invoice 1, for the batch without bundle
|
# POS Invoice 1, for the batch without bundle
|
||||||
pos_inv1 = create_pos_invoice(item="_BATCH ITEM Test For Reserve", rate=300, qty=15, do_not_save=1)
|
pos_inv1 = create_pos_invoice(item="_BATCH ITEM Test For Reserve", rate=300, qty=15, do_not_save=1)
|
||||||
|
pos_inv1.append(
|
||||||
|
"payments",
|
||||||
|
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 4500},
|
||||||
|
)
|
||||||
pos_inv1.items[0].batch_no = batch_no
|
pos_inv1.items[0].batch_no = batch_no
|
||||||
pos_inv1.save()
|
pos_inv1.save()
|
||||||
pos_inv1.submit()
|
pos_inv1.submit()
|
||||||
@@ -798,8 +837,14 @@ class TestPOSInvoice(IntegrationTestCase):
|
|||||||
|
|
||||||
# POS Invoice 2, for the batch with bundle
|
# POS Invoice 2, for the batch with bundle
|
||||||
pos_inv2 = create_pos_invoice(
|
pos_inv2 = create_pos_invoice(
|
||||||
item="_BATCH ITEM Test For Reserve", rate=300, qty=10, batch_no=batch_no
|
item="_BATCH ITEM Test For Reserve", rate=300, qty=10, batch_no=batch_no, do_not_save=1
|
||||||
)
|
)
|
||||||
|
pos_inv2.append(
|
||||||
|
"payments",
|
||||||
|
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3000},
|
||||||
|
)
|
||||||
|
pos_inv2.save()
|
||||||
|
pos_inv2.submit()
|
||||||
pos_inv2.reload()
|
pos_inv2.reload()
|
||||||
self.assertTrue(pos_inv2.items[0].serial_and_batch_bundle)
|
self.assertTrue(pos_inv2.items[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
@@ -834,6 +879,10 @@ class TestPOSInvoice(IntegrationTestCase):
|
|||||||
pos_inv1 = create_pos_invoice(
|
pos_inv1 = create_pos_invoice(
|
||||||
item=item.name, rate=300, qty=1, do_not_submit=1, batch_no="TestBatch 01"
|
item=item.name, rate=300, qty=1, do_not_submit=1, batch_no="TestBatch 01"
|
||||||
)
|
)
|
||||||
|
pos_inv1.append(
|
||||||
|
"payments",
|
||||||
|
{"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 300},
|
||||||
|
)
|
||||||
pos_inv1.save()
|
pos_inv1.save()
|
||||||
pos_inv1.submit()
|
pos_inv1.submit()
|
||||||
|
|
||||||
|
|||||||
@@ -40,14 +40,17 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
|
|||||||
|
|
||||||
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.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_inv2.save()
|
||||||
pos_inv2.submit()
|
pos_inv2.submit()
|
||||||
|
|
||||||
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", 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_inv3.save()
|
||||||
pos_inv3.submit()
|
pos_inv3.submit()
|
||||||
|
|
||||||
consolidate_pos_invoices()
|
consolidate_pos_invoices()
|
||||||
@@ -73,14 +76,17 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
|
|||||||
|
|
||||||
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.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_inv2.save()
|
||||||
pos_inv2.submit()
|
pos_inv2.submit()
|
||||||
|
|
||||||
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", 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_inv3.save()
|
||||||
pos_inv3.submit()
|
pos_inv3.submit()
|
||||||
|
|
||||||
pos_inv_cn = make_sales_return(pos_inv.name)
|
pos_inv_cn = make_sales_return(pos_inv.name)
|
||||||
@@ -135,6 +141,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
|
|||||||
)
|
)
|
||||||
inv.insert()
|
inv.insert()
|
||||||
inv.payments[0].amount = inv.grand_total
|
inv.payments[0].amount = inv.grand_total
|
||||||
|
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)
|
||||||
@@ -152,6 +159,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
|
|||||||
)
|
)
|
||||||
inv2.insert()
|
inv2.insert()
|
||||||
inv2.payments[0].amount = inv.grand_total
|
inv2.payments[0].amount = inv.grand_total
|
||||||
|
inv2.save()
|
||||||
inv2.submit()
|
inv2.submit()
|
||||||
|
|
||||||
consolidate_pos_invoices()
|
consolidate_pos_invoices()
|
||||||
@@ -291,7 +299,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
|
|||||||
inv2.submit()
|
inv2.submit()
|
||||||
|
|
||||||
inv3 = create_pos_invoice(qty=3, rate=600, do_not_save=True)
|
inv3 = create_pos_invoice(qty=3, rate=600, do_not_save=True)
|
||||||
inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000})
|
inv3.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1800})
|
||||||
inv3.insert()
|
inv3.insert()
|
||||||
inv3.submit()
|
inv3.submit()
|
||||||
|
|
||||||
@@ -299,8 +307,8 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
|
|||||||
|
|
||||||
inv.load_from_db()
|
inv.load_from_db()
|
||||||
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
|
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
|
||||||
self.assertEqual(consolidated_invoice.outstanding_amount, 800)
|
self.assertNotEqual(consolidated_invoice.outstanding_amount, 800)
|
||||||
self.assertNotEqual(consolidated_invoice.status, "Paid")
|
self.assertEqual(consolidated_invoice.status, "Paid")
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
@@ -435,6 +443,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
|
|||||||
do_not_submit=1,
|
do_not_submit=1,
|
||||||
)
|
)
|
||||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
|
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
|
||||||
|
pos_inv.save()
|
||||||
pos_inv.submit()
|
pos_inv.submit()
|
||||||
|
|
||||||
pos_inv_cn = make_sales_return(pos_inv.name)
|
pos_inv_cn = make_sales_return(pos_inv.name)
|
||||||
@@ -449,6 +458,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
|
|||||||
do_not_submit=1,
|
do_not_submit=1,
|
||||||
)
|
)
|
||||||
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
|
pos_inv2.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
|
||||||
|
pos_inv2.save()
|
||||||
pos_inv2.submit()
|
pos_inv2.submit()
|
||||||
|
|
||||||
consolidate_pos_invoices()
|
consolidate_pos_invoices()
|
||||||
|
|||||||
@@ -78,7 +78,11 @@ class POSProfile(Document):
|
|||||||
def validate_accounting_dimensions(self):
|
def validate_accounting_dimensions(self):
|
||||||
acc_dims = get_checks_for_pl_and_bs_accounts()
|
acc_dims = get_checks_for_pl_and_bs_accounts()
|
||||||
for acc_dim in acc_dims:
|
for acc_dim in acc_dims:
|
||||||
if not self.get(acc_dim.fieldname) and (acc_dim.mandatory_for_pl or acc_dim.mandatory_for_bs):
|
if (
|
||||||
|
self.company == acc_dim.company
|
||||||
|
and not self.get(acc_dim.fieldname)
|
||||||
|
and (acc_dim.mandatory_for_pl or acc_dim.mandatory_for_bs)
|
||||||
|
):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"{0} is a mandatory Accounting Dimension. <br>"
|
"{0} is a mandatory Accounting Dimension. <br>"
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
"column_break_42",
|
"column_break_42",
|
||||||
"free_item_uom",
|
"free_item_uom",
|
||||||
"round_free_qty",
|
"round_free_qty",
|
||||||
|
"dont_enforce_free_item_qty",
|
||||||
"is_recursive",
|
"is_recursive",
|
||||||
"recurse_for",
|
"recurse_for",
|
||||||
"apply_recursion_over",
|
"apply_recursion_over",
|
||||||
@@ -643,12 +644,19 @@
|
|||||||
"fieldname": "has_priority",
|
"fieldname": "has_priority",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Has Priority"
|
"label": "Has Priority"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.price_or_product_discount == 'Product'",
|
||||||
|
"fieldname": "dont_enforce_free_item_qty",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Don't Enforce Free Item Qty"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-gift",
|
"icon": "fa fa-gift",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-09-16 18:14:51.314765",
|
"modified": "2025-02-17 18:15:39.824639",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Pricing Rule",
|
"name": "Pricing Rule",
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class PricingRule(Document):
|
|||||||
disable: DF.Check
|
disable: DF.Check
|
||||||
discount_amount: DF.Currency
|
discount_amount: DF.Currency
|
||||||
discount_percentage: DF.Float
|
discount_percentage: DF.Float
|
||||||
|
dont_enforce_free_item_qty: DF.Check
|
||||||
for_price_list: DF.Link | None
|
for_price_list: DF.Link | None
|
||||||
free_item: DF.Link | None
|
free_item: DF.Link | None
|
||||||
free_item_rate: DF.Currency
|
free_item_rate: DF.Currency
|
||||||
@@ -645,7 +646,7 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, ra
|
|||||||
if pricing_rule.margin_type in ["Percentage", "Amount"]:
|
if pricing_rule.margin_type in ["Percentage", "Amount"]:
|
||||||
item_details.margin_rate_or_amount = 0.0
|
item_details.margin_rate_or_amount = 0.0
|
||||||
item_details.margin_type = None
|
item_details.margin_type = None
|
||||||
elif pricing_rule.get("free_item"):
|
elif pricing_rule.get("free_item") and not pricing_rule.get("dont_enforce_free_item_qty"):
|
||||||
item_details.remove_free_item = (
|
item_details.remove_free_item = (
|
||||||
item_code if pricing_rule.get("same_item") else pricing_rule.get("free_item")
|
item_code if pricing_rule.get("same_item") else pricing_rule.get("free_item")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -438,6 +438,54 @@ class TestPricingRule(IntegrationTestCase):
|
|||||||
self.assertEqual(so.items[1].is_free_item, 1)
|
self.assertEqual(so.items[1].is_free_item, 1)
|
||||||
self.assertEqual(so.items[1].item_code, "_Test Item 2")
|
self.assertEqual(so.items[1].item_code, "_Test Item 2")
|
||||||
|
|
||||||
|
def test_dont_enforce_free_item_qty(self):
|
||||||
|
# this test is only for testing non-enforcement as all other tests in this file already test with enforcement
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
|
||||||
|
test_record = {
|
||||||
|
"doctype": "Pricing Rule",
|
||||||
|
"title": "_Test Pricing Rule",
|
||||||
|
"apply_on": "Item Code",
|
||||||
|
"currency": "USD",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"item_code": "_Test Item",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selling": 1,
|
||||||
|
"rate_or_discount": "Discount Percentage",
|
||||||
|
"rate": 0,
|
||||||
|
"min_qty": 0,
|
||||||
|
"max_qty": 7,
|
||||||
|
"discount_percentage": 17.5,
|
||||||
|
"price_or_product_discount": "Product",
|
||||||
|
"same_item": 0,
|
||||||
|
"free_item": "_Test Item 2",
|
||||||
|
"free_qty": 1,
|
||||||
|
"company": "_Test Company",
|
||||||
|
}
|
||||||
|
pricing_rule = frappe.get_doc(test_record.copy()).insert()
|
||||||
|
|
||||||
|
# With enforcement
|
||||||
|
so = make_sales_order(item_code="_Test Item", qty=1, do_not_submit=True)
|
||||||
|
self.assertEqual(so.items[1].is_free_item, 1)
|
||||||
|
self.assertEqual(so.items[1].item_code, "_Test Item 2")
|
||||||
|
|
||||||
|
# Test 1 : Saving a document with an item with pricing list without it's corresponding free item will cause it the free item to be refetched on save
|
||||||
|
so.items.pop(1)
|
||||||
|
so.save()
|
||||||
|
so.reload()
|
||||||
|
self.assertEqual(len(so.items), 2)
|
||||||
|
|
||||||
|
# Without enforcement
|
||||||
|
pricing_rule.dont_enforce_free_item_qty = 1
|
||||||
|
pricing_rule.save()
|
||||||
|
|
||||||
|
# Test 2 : Deleted free item will not be fetched again on save without enforcement
|
||||||
|
so.items.pop(1)
|
||||||
|
so.save()
|
||||||
|
so.reload()
|
||||||
|
self.assertEqual(len(so.items), 1)
|
||||||
|
|
||||||
def test_cumulative_pricing_rule(self):
|
def test_cumulative_pricing_rule(self):
|
||||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Cumulative Pricing Rule")
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Cumulative Pricing Rule")
|
||||||
test_record = {
|
test_record = {
|
||||||
@@ -1461,6 +1509,7 @@ def make_pricing_rule(**args):
|
|||||||
"discount_amount": args.discount_amount or 0.0,
|
"discount_amount": args.discount_amount or 0.0,
|
||||||
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0,
|
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0,
|
||||||
"has_priority": args.has_priority or 0,
|
"has_priority": args.has_priority or 0,
|
||||||
|
"enforce_free_item_qty": args.dont_enforce_free_item_qty or 0,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -713,7 +713,10 @@ def apply_pricing_rule_for_free_items(doc, pricing_rule_args):
|
|||||||
args.pop((item.item_code, item.pricing_rules))
|
args.pop((item.item_code, item.pricing_rules))
|
||||||
|
|
||||||
for free_item in args.values():
|
for free_item in args.values():
|
||||||
doc.append("items", free_item)
|
if doc.is_new() or not frappe.get_value(
|
||||||
|
"Pricing Rule", free_item["pricing_rules"], "dont_enforce_free_item_qty"
|
||||||
|
):
|
||||||
|
doc.append("items", free_item)
|
||||||
|
|
||||||
|
|
||||||
def get_pricing_rule_items(pr_doc, other_items=False) -> list:
|
def get_pricing_rule_items(pr_doc, other_items=False) -> list:
|
||||||
|
|||||||
@@ -236,17 +236,21 @@ def get_ar_filters(doc, entry):
|
|||||||
|
|
||||||
def get_html(doc, filters, entry, col, res, ageing):
|
def get_html(doc, filters, entry, col, res, ageing):
|
||||||
base_template_path = "frappe/www/printview.html"
|
base_template_path = "frappe/www/printview.html"
|
||||||
template_path = (
|
template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html"
|
||||||
"erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
|
if doc.report == "General Ledger":
|
||||||
if doc.report == "General Ledger"
|
template_path = (
|
||||||
else "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html"
|
"erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
process_soa_html = frappe.get_hooks("process_soa_html")
|
||||||
|
# fetching custom print format for Process Statement of Accounts
|
||||||
|
if process_soa_html and process_soa_html.get(doc.report):
|
||||||
|
template_path = process_soa_html[doc.report][-1]
|
||||||
|
|
||||||
if doc.letter_head:
|
if doc.letter_head:
|
||||||
from frappe.www.printview import get_letter_head
|
from frappe.www.printview import get_letter_head
|
||||||
|
|
||||||
letter_head = get_letter_head(doc, 0)
|
letter_head = get_letter_head(doc, 0)
|
||||||
|
|
||||||
html = frappe.render_template(
|
html = frappe.render_template(
|
||||||
template_path,
|
template_path,
|
||||||
{
|
{
|
||||||
@@ -262,7 +266,6 @@ def get_html(doc, filters, entry, col, res, ageing):
|
|||||||
else None,
|
else None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
html = frappe.render_template(
|
html = frappe.render_template(
|
||||||
base_template_path,
|
base_template_path,
|
||||||
{"body": html, "css": get_print_style(), "title": "Statement For " + entry.customer},
|
{"body": html, "css": get_print_style(), "title": "Statement For " + entry.customer},
|
||||||
|
|||||||
@@ -777,7 +777,7 @@ def validate_account_party_type(self):
|
|||||||
|
|
||||||
if self.party_type and self.party:
|
if self.party_type and self.party:
|
||||||
account_type = frappe.get_cached_value("Account", self.account, "account_type")
|
account_type = frappe.get_cached_value("Account", self.account, "account_type")
|
||||||
if account_type and (account_type not in ["Receivable", "Payable"]):
|
if account_type and (account_type not in ["Receivable", "Payable", "Equity"]):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Party Type and Party can only be set for Receivable / Payable account<br><br>" "{0}"
|
"Party Type and Party can only be set for Receivable / Payable account<br><br>" "{0}"
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="right" colspan="3" ><strong>Total (debit) </strong></td>
|
<td class="right" colspan="3" ><strong>Total (debit) </strong></td>
|
||||||
<td class="left" >{{ gl | sum(attribute='debit') }}</td>
|
<td class="left" >{{ gl | sum(attribute='debit') | round(2) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="top-bottom" colspan="5"><strong>Credit</strong></td>
|
<td class="top-bottom" colspan="5"><strong>Credit</strong></td>
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="right" colspan="3"><strong>Total (credit) </strong></td>
|
<td class="right" colspan="3"><strong>Total (credit) </strong></td>
|
||||||
<td class="left" >{{ gl | sum(attribute='credit') }}</td>
|
<td class="left" >{{ gl | sum(attribute='credit') | round(2) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="top-bottom" colspan="5"><b>Narration: </b>{{ gl[0].remarks }}</td>
|
<td class="top-bottom" colspan="5"><b>Narration: </b>{{ gl[0].remarks }}</td>
|
||||||
|
|||||||
@@ -68,16 +68,12 @@ frappe.query_reports["Trial Balance for Party"] = {
|
|||||||
{
|
{
|
||||||
fieldname: "account",
|
fieldname: "account",
|
||||||
label: __("Account"),
|
label: __("Account"),
|
||||||
fieldtype: "Link",
|
fieldtype: "MultiSelectList",
|
||||||
options: "Account",
|
options: "Account",
|
||||||
get_query: function () {
|
get_data: function (txt) {
|
||||||
var company = frappe.query_report.get_filter_value("company");
|
return frappe.db.get_link_options("Account", txt, {
|
||||||
return {
|
company: frappe.query_report.get_filter_value("company"),
|
||||||
doctype: "Account",
|
});
|
||||||
filters: {
|
|
||||||
company: company,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import cint, flt
|
from frappe.utils import cint, flt
|
||||||
|
|
||||||
|
from erpnext.accounts.report.general_ledger.general_ledger import get_accounts_with_children
|
||||||
from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
|
from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
|
||||||
|
|
||||||
|
|
||||||
@@ -35,9 +37,14 @@ def get_data(filters, show_party_name):
|
|||||||
filters=party_filters,
|
filters=party_filters,
|
||||||
order_by="name",
|
order_by="name",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
account_filter = []
|
||||||
|
if filters.get("account"):
|
||||||
|
account_filter = get_accounts_with_children(filters.get("account"))
|
||||||
|
|
||||||
company_currency = frappe.get_cached_value("Company", filters.company, "default_currency")
|
company_currency = frappe.get_cached_value("Company", filters.company, "default_currency")
|
||||||
opening_balances = get_opening_balances(filters)
|
opening_balances = get_opening_balances(filters, account_filter)
|
||||||
balances_within_period = get_balances_within_period(filters)
|
balances_within_period = get_balances_within_period(filters, account_filter)
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
# total_debit, total_credit = 0, 0
|
# total_debit, total_credit = 0, 0
|
||||||
@@ -89,30 +96,34 @@ def get_data(filters, show_party_name):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_opening_balances(filters):
|
def get_opening_balances(filters, account_filter=None):
|
||||||
account_filter = ""
|
GL_Entry = frappe.qb.DocType("GL Entry")
|
||||||
if filters.get("account"):
|
|
||||||
account_filter = "and account = %s" % (frappe.db.escape(filters.get("account")))
|
|
||||||
|
|
||||||
gle = frappe.db.sql(
|
query = (
|
||||||
f"""
|
frappe.qb.from_(GL_Entry)
|
||||||
select party, sum(debit) as opening_debit, sum(credit) as opening_credit
|
.select(
|
||||||
from `tabGL Entry`
|
GL_Entry.party,
|
||||||
where company=%(company)s
|
Sum(GL_Entry.debit).as_("opening_debit"),
|
||||||
and is_cancelled=0
|
Sum(GL_Entry.credit).as_("opening_credit"),
|
||||||
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
|
)
|
||||||
and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s))
|
.where(
|
||||||
{account_filter}
|
(GL_Entry.company == filters.company)
|
||||||
group by party""",
|
& (GL_Entry.is_cancelled == 0)
|
||||||
{
|
& (GL_Entry.party_type == filters.party_type)
|
||||||
"company": filters.company,
|
& (GL_Entry.party != "")
|
||||||
"from_date": filters.from_date,
|
& (
|
||||||
"to_date": filters.to_date,
|
(GL_Entry.posting_date < filters.from_date)
|
||||||
"party_type": filters.party_type,
|
| ((GL_Entry.is_opening == "Yes") & (GL_Entry.posting_date <= filters.to_date))
|
||||||
},
|
)
|
||||||
as_dict=True,
|
)
|
||||||
|
.groupby(GL_Entry.party)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if account_filter:
|
||||||
|
query = query.where(GL_Entry.account.isin(account_filter))
|
||||||
|
|
||||||
|
gle = query.run(as_dict=True)
|
||||||
|
|
||||||
opening = frappe._dict()
|
opening = frappe._dict()
|
||||||
for d in gle:
|
for d in gle:
|
||||||
opening_debit, opening_credit = toggle_debit_credit(d.opening_debit, d.opening_credit)
|
opening_debit, opening_credit = toggle_debit_credit(d.opening_debit, d.opening_credit)
|
||||||
@@ -121,31 +132,33 @@ def get_opening_balances(filters):
|
|||||||
return opening
|
return opening
|
||||||
|
|
||||||
|
|
||||||
def get_balances_within_period(filters):
|
def get_balances_within_period(filters, account_filter=None):
|
||||||
account_filter = ""
|
GL_Entry = frappe.qb.DocType("GL Entry")
|
||||||
if filters.get("account"):
|
|
||||||
account_filter = "and account = %s" % (frappe.db.escape(filters.get("account")))
|
|
||||||
|
|
||||||
gle = frappe.db.sql(
|
query = (
|
||||||
f"""
|
frappe.qb.from_(GL_Entry)
|
||||||
select party, sum(debit) as debit, sum(credit) as credit
|
.select(
|
||||||
from `tabGL Entry`
|
GL_Entry.party,
|
||||||
where company=%(company)s
|
Sum(GL_Entry.debit).as_("debit"),
|
||||||
and is_cancelled = 0
|
Sum(GL_Entry.credit).as_("credit"),
|
||||||
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
|
)
|
||||||
and posting_date >= %(from_date)s and posting_date <= %(to_date)s
|
.where(
|
||||||
and ifnull(is_opening, 'No') = 'No'
|
(GL_Entry.company == filters.company)
|
||||||
{account_filter}
|
& (GL_Entry.is_cancelled == 0)
|
||||||
group by party""",
|
& (GL_Entry.party_type == filters.party_type)
|
||||||
{
|
& (GL_Entry.party != "")
|
||||||
"company": filters.company,
|
& (GL_Entry.posting_date >= filters.from_date)
|
||||||
"from_date": filters.from_date,
|
& (GL_Entry.posting_date <= filters.to_date)
|
||||||
"to_date": filters.to_date,
|
& (GL_Entry.is_opening == "No")
|
||||||
"party_type": filters.party_type,
|
)
|
||||||
},
|
.groupby(GL_Entry.party)
|
||||||
as_dict=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if account_filter:
|
||||||
|
query = query.where(GL_Entry.account.isin(account_filter))
|
||||||
|
|
||||||
|
gle = query.run(as_dict=True)
|
||||||
|
|
||||||
balances_within_period = frappe._dict()
|
balances_within_period = frappe._dict()
|
||||||
for d in gle:
|
for d in gle:
|
||||||
balances_within_period.setdefault(d.party, [d.debit, d.credit])
|
balances_within_period.setdefault(d.party, [d.debit, d.credit])
|
||||||
|
|||||||
@@ -609,9 +609,7 @@ frappe.ui.form.on("Asset", {
|
|||||||
frm.trigger("toggle_reference_doc");
|
frm.trigger("toggle_reference_doc");
|
||||||
if (frm.doc.purchase_receipt) {
|
if (frm.doc.purchase_receipt) {
|
||||||
if (frm.doc.item_code) {
|
if (frm.doc.item_code) {
|
||||||
frappe.db.get_doc("Purchase Receipt", frm.doc.purchase_receipt).then((pr_doc) => {
|
frm.events.set_values_from_purchase_doc(frm, "Purchase Receipt");
|
||||||
frm.events.set_values_from_purchase_doc(frm, "Purchase Receipt", pr_doc);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
frm.set_value("purchase_receipt", "");
|
frm.set_value("purchase_receipt", "");
|
||||||
frappe.msgprint({
|
frappe.msgprint({
|
||||||
@@ -626,9 +624,7 @@ frappe.ui.form.on("Asset", {
|
|||||||
frm.trigger("toggle_reference_doc");
|
frm.trigger("toggle_reference_doc");
|
||||||
if (frm.doc.purchase_invoice) {
|
if (frm.doc.purchase_invoice) {
|
||||||
if (frm.doc.item_code) {
|
if (frm.doc.item_code) {
|
||||||
frappe.db.get_doc("Purchase Invoice", frm.doc.purchase_invoice).then((pi_doc) => {
|
frm.events.set_values_from_purchase_doc(frm, "Purchase Invoice");
|
||||||
frm.events.set_values_from_purchase_doc(frm, "Purchase Invoice", pi_doc);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
frm.set_value("purchase_invoice", "");
|
frm.set_value("purchase_invoice", "");
|
||||||
frappe.msgprint({
|
frappe.msgprint({
|
||||||
@@ -639,45 +635,35 @@ frappe.ui.form.on("Asset", {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
set_values_from_purchase_doc: function (frm, doctype, purchase_doc) {
|
set_values_from_purchase_doc: (frm, doctype) => {
|
||||||
frm.set_value("company", purchase_doc.company);
|
frappe.call({
|
||||||
if (purchase_doc.bill_date) {
|
method: "erpnext.assets.doctype.asset.asset.get_values_from_purchase_doc",
|
||||||
frm.set_value("purchase_date", purchase_doc.bill_date);
|
args: {
|
||||||
} else {
|
purchase_doc_name: frm.doc.purchase_receipt || frm.doc.purchase_invoice,
|
||||||
frm.set_value("purchase_date", purchase_doc.posting_date);
|
item_code: frm.doc.item_code,
|
||||||
}
|
doctype: doctype,
|
||||||
if (!frm.doc.is_existing_asset && !frm.doc.available_for_use_date) {
|
},
|
||||||
frm.set_value("available_for_use_date", frm.doc.purchase_date);
|
callback: (r) => {
|
||||||
}
|
if (r.message) {
|
||||||
const item = purchase_doc.items.find((item) => item.item_code === frm.doc.item_code);
|
let data = r.message;
|
||||||
if (!item) {
|
frm.set_value("company", data.company);
|
||||||
let doctype_field = frappe.scrub(doctype);
|
frm.set_value("purchase_date", data.purchase_date);
|
||||||
frm.set_value(doctype_field, "");
|
frm.set_value("gross_purchase_amount", data.gross_purchase_amount);
|
||||||
frappe.msgprint({
|
frm.set_value("purchase_amount", data.gross_purchase_amount);
|
||||||
title: __("Invalid {0}", [__(doctype)]),
|
frm.set_value("asset_quantity", data.asset_quantity);
|
||||||
message: __("The selected {0} does not contain the selected Asset Item.", [__(doctype)]),
|
frm.set_value("cost_center", data.cost_center);
|
||||||
indicator: "red",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
frappe.db.get_value("Item", item.item_code, "is_grouped_asset", (r) => {
|
|
||||||
var asset_quantity = r.is_grouped_asset ? item.qty : 1;
|
|
||||||
var purchase_amount = flt(
|
|
||||||
item.valuation_rate * asset_quantity,
|
|
||||||
precision("gross_purchase_amount")
|
|
||||||
);
|
|
||||||
|
|
||||||
frm.set_value("gross_purchase_amount", purchase_amount);
|
if (doctype === "Purchase Receipt") {
|
||||||
frm.set_value("purchase_amount", purchase_amount);
|
frm.set_value("purchase_receipt_item", data.purchase_receipt_item);
|
||||||
frm.set_value("asset_quantity", asset_quantity);
|
} else {
|
||||||
frm.set_value("cost_center", item.cost_center || purchase_doc.cost_center);
|
frm.set_value("purchase_invoice_item", data.purchase_invoice_item);
|
||||||
if (item.asset_location) {
|
}
|
||||||
frm.set_value("location", item.asset_location);
|
|
||||||
}
|
let is_editable = !data.is_multiple_items; // if multiple items, then fields should be read-only
|
||||||
if (doctype === "Purchase Receipt") {
|
frm.set_df_property("gross_purchase_amount", "read_only", is_editable);
|
||||||
frm.set_value("purchase_receipt_item", item.name);
|
frm.set_df_property("asset_quantity", "read_only", is_editable);
|
||||||
} else if (doctype === "Purchase Invoice") {
|
}
|
||||||
frm.set_value("purchase_invoice_item", item.name);
|
},
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -228,8 +228,7 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Gross Purchase Amount",
|
"label": "Gross Purchase Amount",
|
||||||
"mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)",
|
"mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)",
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency"
|
||||||
"read_only_depends_on": "eval:!doc.is_existing_asset"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "available_for_use_date",
|
"fieldname": "available_for_use_date",
|
||||||
@@ -436,8 +435,7 @@
|
|||||||
"default": "1",
|
"default": "1",
|
||||||
"fieldname": "asset_quantity",
|
"fieldname": "asset_quantity",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Asset Quantity",
|
"label": "Asset Quantity"
|
||||||
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "depr_entry_posting_status",
|
"fieldname": "depr_entry_posting_status",
|
||||||
@@ -507,17 +505,15 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "purchase_receipt_item",
|
"fieldname": "purchase_receipt_item",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Purchase Receipt Item",
|
"label": "Purchase Receipt Item"
|
||||||
"options": "Purchase Receipt Item"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "purchase_invoice_item",
|
"fieldname": "purchase_invoice_item",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Purchase Invoice Item",
|
"label": "Purchase Invoice Item"
|
||||||
"options": "Purchase Invoice Item"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "insurance_details_tab",
|
"fieldname": "insurance_details_tab",
|
||||||
@@ -592,7 +588,7 @@
|
|||||||
"link_fieldname": "target_asset"
|
"link_fieldname": "target_asset"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2024-12-26 14:23:20.968882",
|
"modified": "2025-02-11 16:01:56.140904",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset",
|
"name": "Asset",
|
||||||
|
|||||||
@@ -95,9 +95,9 @@ class Asset(AccountsController):
|
|||||||
purchase_amount: DF.Currency
|
purchase_amount: DF.Currency
|
||||||
purchase_date: DF.Date | None
|
purchase_date: DF.Date | None
|
||||||
purchase_invoice: DF.Link | None
|
purchase_invoice: DF.Link | None
|
||||||
purchase_invoice_item: DF.Link | None
|
purchase_invoice_item: DF.Data | None
|
||||||
purchase_receipt: DF.Link | None
|
purchase_receipt: DF.Link | None
|
||||||
purchase_receipt_item: DF.Link | None
|
purchase_receipt_item: DF.Data | None
|
||||||
split_from: DF.Link | None
|
split_from: DF.Link | None
|
||||||
status: DF.Literal[
|
status: DF.Literal[
|
||||||
"Draft",
|
"Draft",
|
||||||
@@ -121,6 +121,7 @@ class Asset(AccountsController):
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_precision()
|
self.validate_precision()
|
||||||
|
self.set_purchase_doc_row_item()
|
||||||
self.validate_asset_values()
|
self.validate_asset_values()
|
||||||
self.validate_asset_and_reference()
|
self.validate_asset_and_reference()
|
||||||
self.validate_item()
|
self.validate_item()
|
||||||
@@ -200,6 +201,38 @@ class Asset(AccountsController):
|
|||||||
def after_delete(self):
|
def after_delete(self):
|
||||||
add_asset_activity(self.name, _("Asset deleted"))
|
add_asset_activity(self.name, _("Asset deleted"))
|
||||||
|
|
||||||
|
def set_purchase_doc_row_item(self):
|
||||||
|
if self.is_existing_asset or self.is_composite_asset:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.purchase_amount = self.gross_purchase_amount
|
||||||
|
purchase_doc_type = "Purchase Receipt" if self.purchase_receipt else "Purchase Invoice"
|
||||||
|
purchase_doc = self.purchase_receipt or self.purchase_invoice
|
||||||
|
|
||||||
|
if not purchase_doc:
|
||||||
|
return
|
||||||
|
|
||||||
|
linked_item = self.get_linked_item(purchase_doc_type, purchase_doc)
|
||||||
|
|
||||||
|
if linked_item:
|
||||||
|
if purchase_doc_type == "Purchase Receipt":
|
||||||
|
self.purchase_receipt_item = linked_item
|
||||||
|
else:
|
||||||
|
self.purchase_invoice_item = linked_item
|
||||||
|
|
||||||
|
def get_linked_item(self, purchase_doc_type, purchase_doc):
|
||||||
|
purchase_doc = frappe.get_doc(purchase_doc_type, purchase_doc)
|
||||||
|
|
||||||
|
for item in purchase_doc.items:
|
||||||
|
if self.asset_quantity > 1:
|
||||||
|
if item.base_net_amount == self.gross_purchase_amount and item.qty == self.asset_quantity:
|
||||||
|
return item.name
|
||||||
|
elif item.qty == self.asset_quantity:
|
||||||
|
return item.name
|
||||||
|
else:
|
||||||
|
if item.base_net_rate == self.gross_purchase_amount and item.qty == self.asset_quantity:
|
||||||
|
return item.name
|
||||||
|
|
||||||
def validate_asset_and_reference(self):
|
def validate_asset_and_reference(self):
|
||||||
if self.purchase_invoice or self.purchase_receipt:
|
if self.purchase_invoice or self.purchase_receipt:
|
||||||
reference_doc = "Purchase Invoice" if self.purchase_invoice else "Purchase Receipt"
|
reference_doc = "Purchase Invoice" if self.purchase_invoice else "Purchase Receipt"
|
||||||
@@ -1126,6 +1159,30 @@ def has_active_capitalization(asset):
|
|||||||
return active_capitalizations > 0
|
return active_capitalizations > 0
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_values_from_purchase_doc(purchase_doc_name, item_code, doctype):
|
||||||
|
purchase_doc = frappe.get_doc(doctype, purchase_doc_name)
|
||||||
|
matching_items = [item for item in purchase_doc.items if item.item_code == item_code]
|
||||||
|
|
||||||
|
if not matching_items:
|
||||||
|
frappe.throw(_(f"Selected {doctype} does not contain the Item Code {item_code}"))
|
||||||
|
|
||||||
|
first_item = matching_items[0]
|
||||||
|
is_multiple_items = len(matching_items) > 1
|
||||||
|
|
||||||
|
return {
|
||||||
|
"company": purchase_doc.company,
|
||||||
|
"purchase_date": purchase_doc.get("bill_date") or purchase_doc.get("posting_date"),
|
||||||
|
"gross_purchase_amount": flt(first_item.base_net_amount),
|
||||||
|
"asset_quantity": first_item.qty,
|
||||||
|
"cost_center": first_item.cost_center or purchase_doc.get("cost_center"),
|
||||||
|
"asset_location": first_item.get("asset_location"),
|
||||||
|
"is_multiple_items": is_multiple_items,
|
||||||
|
"purchase_receipt_item": first_item.name if doctype == "Purchase Receipt" else None,
|
||||||
|
"purchase_invoice_item": first_item.name if doctype == "Purchase Invoice" else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def split_asset(asset_name, split_qty):
|
def split_asset(asset_name, split_qty):
|
||||||
asset = frappe.get_doc("Asset", asset_name)
|
asset = frappe.get_doc("Asset", asset_name)
|
||||||
|
|||||||
@@ -448,9 +448,9 @@ def scrap_asset(asset_name, scrap_date=None):
|
|||||||
notes = _("This schedule was created when Asset {0} was scrapped.").format(
|
notes = _("This schedule was created when Asset {0} was scrapped.").format(
|
||||||
get_link_to_form(asset.doctype, asset.name)
|
get_link_to_form(asset.doctype, asset.name)
|
||||||
)
|
)
|
||||||
|
if asset.status != "Fully Depreciated":
|
||||||
depreciate_asset(asset, date, notes)
|
depreciate_asset(asset, date, notes)
|
||||||
asset.reload()
|
asset.reload()
|
||||||
|
|
||||||
depreciation_series = frappe.get_cached_value("Company", asset.company, "series_for_depreciation_entry")
|
depreciation_series = frappe.get_cached_value("Company", asset.company, "series_for_depreciation_entry")
|
||||||
|
|
||||||
|
|||||||
@@ -937,6 +937,7 @@
|
|||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Subcontracted Quantity",
|
"label": "Subcontracted Quantity",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
|
"non_negative": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -944,7 +945,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-12-10 12:11:18.536089",
|
"modified": "2025-02-18 12:35:04.432636",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order Item",
|
"name": "Purchase Order Item",
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ class AccountsController(TransactionBase):
|
|||||||
self.validate_qty_is_not_zero()
|
self.validate_qty_is_not_zero()
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.doctype in ["Sales Invoice", "Purchase Invoice"]
|
self.doctype in ["Sales Invoice", "Purchase Invoice", "POS Invoice"]
|
||||||
and self.get("is_return")
|
and self.get("is_return")
|
||||||
and self.get("update_stock")
|
and self.get("update_stock")
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -780,8 +780,10 @@ class BuyingController(SubcontractingController):
|
|||||||
|
|
||||||
is_plural = "s" if len(created_assets) != 1 else ""
|
is_plural = "s" if len(created_assets) != 1 else ""
|
||||||
messages.append(
|
messages.append(
|
||||||
_("Asset{} {assets_link} created for {}").format(
|
_("Asset{is_plural} {assets_link} created for {item_code}").format(
|
||||||
is_plural, frappe.bold(d.item_code), assets_link=assets_link
|
is_plural=is_plural,
|
||||||
|
assets_link=assets_link,
|
||||||
|
item_code=frappe.bold(d.item_code),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ def get_already_returned_items(doc):
|
|||||||
|
|
||||||
field = (
|
field = (
|
||||||
frappe.scrub(doc.doctype) + "_item"
|
frappe.scrub(doc.doctype) + "_item"
|
||||||
if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Sales Invoice"]
|
if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Sales Invoice", "POS Invoice"]
|
||||||
else "dn_detail"
|
else "dn_detail"
|
||||||
)
|
)
|
||||||
data = frappe.db.sql(
|
data = frappe.db.sql(
|
||||||
@@ -770,6 +770,7 @@ def get_return_against_item_fields(voucher_type):
|
|||||||
"Delivery Note": "dn_detail",
|
"Delivery Note": "dn_detail",
|
||||||
"Sales Invoice": "sales_invoice_item",
|
"Sales Invoice": "sales_invoice_item",
|
||||||
"Subcontracting Receipt": "subcontracting_receipt_item",
|
"Subcontracting Receipt": "subcontracting_receipt_item",
|
||||||
|
"POS Invoice": "sales_invoice_item",
|
||||||
}
|
}
|
||||||
return return_against_item_fields[voucher_type]
|
return return_against_item_fields[voucher_type]
|
||||||
|
|
||||||
@@ -1162,3 +1163,29 @@ def get_available_serial_nos(serial_nos, warehouse):
|
|||||||
def get_payment_data(invoice):
|
def get_payment_data(invoice):
|
||||||
payment = frappe.db.get_all("Sales Invoice Payment", {"parent": invoice}, ["mode_of_payment", "amount"])
|
payment = frappe.db.get_all("Sales Invoice Payment", {"parent": invoice}, ["mode_of_payment", "amount"])
|
||||||
return payment
|
return payment
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_pos_invoice_item_returned_qty(pos_invoice, customer, item_row_name):
|
||||||
|
is_return, docstatus = frappe.db.get_value("POS Invoice", pos_invoice, ["is_return", "docstatus"])
|
||||||
|
if not is_return and docstatus == 1:
|
||||||
|
return get_returned_qty_map_for_row(pos_invoice, customer, item_row_name, "POS Invoice")
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def is_pos_invoice_returnable(pos_invoice):
|
||||||
|
is_return, docstatus, customer = frappe.db.get_value(
|
||||||
|
"POS Invoice", pos_invoice, ["is_return", "docstatus", "customer"]
|
||||||
|
)
|
||||||
|
if is_return or docstatus == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
invoice_item_qty = frappe.db.get_all("POS Invoice Item", {"parent": pos_invoice}, ["name", "qty"])
|
||||||
|
|
||||||
|
already_full_returned = 0
|
||||||
|
for d in invoice_item_qty:
|
||||||
|
returned_qty = get_returned_qty_map_for_row(pos_invoice, customer, d.name, "POS Invoice")
|
||||||
|
if returned_qty.qty == d.qty:
|
||||||
|
already_full_returned += 1
|
||||||
|
|
||||||
|
return len(invoice_item_qty) != already_full_returned
|
||||||
|
|||||||
@@ -593,12 +593,13 @@ class SellingController(StockController):
|
|||||||
if not self.is_internal_transfer() or self.docstatus == 1
|
if not self.is_internal_transfer() or self.docstatus == 1
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
if serial_and_batch_bundle and self.is_internal_transfer() and self.is_return:
|
|
||||||
if self.docstatus == 1:
|
if self.is_internal_transfer():
|
||||||
|
if serial_and_batch_bundle and self.docstatus == 1 and self.is_return:
|
||||||
serial_and_batch_bundle = self.make_package_for_transfer(
|
serial_and_batch_bundle = self.make_package_for_transfer(
|
||||||
serial_and_batch_bundle, item_row.warehouse, type_of_transaction="Inward"
|
serial_and_batch_bundle, item_row.warehouse, type_of_transaction="Inward"
|
||||||
)
|
)
|
||||||
else:
|
elif not serial_and_batch_bundle:
|
||||||
serial_and_batch_bundle = frappe.db.get_value(
|
serial_and_batch_bundle = frappe.db.get_value(
|
||||||
"Stock Ledger Entry",
|
"Stock Ledger Entry",
|
||||||
{"voucher_detail_no": item_row.name, "warehouse": item_row.warehouse},
|
{"voucher_detail_no": item_row.name, "warehouse": item_row.warehouse},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import comma_or, flt, get_link_to_form, getdate, now, nowdate
|
from frappe.utils import comma_or, flt, get_link_to_form, getdate, now, nowdate, safe_div
|
||||||
|
|
||||||
|
|
||||||
class OverAllowanceError(frappe.ValidationError):
|
class OverAllowanceError(frappe.ValidationError):
|
||||||
@@ -616,7 +616,7 @@ class StatusUpdater(Document):
|
|||||||
)[0][0]
|
)[0][0]
|
||||||
)
|
)
|
||||||
|
|
||||||
per_billed = (min(ref_doc_qty, billed_qty) / ref_doc_qty) * 100
|
per_billed = safe_div(min(ref_doc_qty, billed_qty), ref_doc_qty) * 100
|
||||||
|
|
||||||
ref_doc = frappe.get_doc(ref_dt, ref_dn)
|
ref_doc = frappe.get_doc(ref_dt, ref_dn)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ app_home = "/app/home"
|
|||||||
add_to_apps_screen = [
|
add_to_apps_screen = [
|
||||||
{
|
{
|
||||||
"name": app_name,
|
"name": app_name,
|
||||||
"logo": "/assets/erpnext/images/erpnext-logo-blue.png",
|
"logo": "/assets/erpnext/images/erpnext-logo.svg",
|
||||||
"title": app_title,
|
"title": app_title,
|
||||||
"route": app_home,
|
"route": app_home,
|
||||||
"has_permission": "erpnext.check_app_permission",
|
"has_permission": "erpnext.check_app_permission",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
2763
erpnext/locale/bs.po
2763
erpnext/locale/bs.po
File diff suppressed because it is too large
Load Diff
1112
erpnext/locale/de.po
1112
erpnext/locale/de.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -368,16 +368,8 @@ def get_children(doctype=None, parent=None, **kwargs):
|
|||||||
"uom",
|
"uom",
|
||||||
"rate",
|
"rate",
|
||||||
"amount",
|
"amount",
|
||||||
"workstation_type",
|
|
||||||
"operation",
|
"operation",
|
||||||
"operation_time",
|
|
||||||
"is_subcontracted",
|
"is_subcontracted",
|
||||||
"workstation",
|
|
||||||
"source_warehouse",
|
|
||||||
"wip_warehouse",
|
|
||||||
"fg_warehouse",
|
|
||||||
"skip_material_transfer",
|
|
||||||
"backflush_from_wip_warehouse",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
query_filters = {
|
query_filters = {
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ class JobCard(Document):
|
|||||||
|
|
||||||
open_job_cards = []
|
open_job_cards = []
|
||||||
if d.get("employee"):
|
if d.get("employee"):
|
||||||
open_job_cards = self.get_open_job_cards(d.get("employee"))
|
open_job_cards = self.get_open_job_cards(d.get("employee"), workstation=self.workstation)
|
||||||
|
|
||||||
data = self.get_overlap_for(d, open_job_cards=open_job_cards)
|
data = self.get_overlap_for(d, open_job_cards=open_job_cards)
|
||||||
if data:
|
if data:
|
||||||
@@ -292,9 +292,13 @@ class JobCard(Document):
|
|||||||
frappe.get_cached_value("Workstation", self.workstation, "production_capacity") or 1
|
frappe.get_cached_value("Workstation", self.workstation, "production_capacity") or 1
|
||||||
)
|
)
|
||||||
|
|
||||||
if args.get("employee"):
|
if self.get_open_job_cards(args.get("employee")):
|
||||||
# override capacity for employee
|
frappe.throw(
|
||||||
production_capacity = 1
|
_(
|
||||||
|
"Employee {0} is currently working on another workstation. Please assign another employee."
|
||||||
|
).format(args.get("employee")),
|
||||||
|
OverlapError,
|
||||||
|
)
|
||||||
|
|
||||||
if not self.has_overlap(production_capacity, time_logs):
|
if not self.has_overlap(production_capacity, time_logs):
|
||||||
return {}
|
return {}
|
||||||
@@ -401,7 +405,7 @@ class JobCard(Document):
|
|||||||
|
|
||||||
return time_logs
|
return time_logs
|
||||||
|
|
||||||
def get_open_job_cards(self, employee):
|
def get_open_job_cards(self, employee, workstation=None):
|
||||||
jc = frappe.qb.DocType("Job Card")
|
jc = frappe.qb.DocType("Job Card")
|
||||||
jctl = frappe.qb.DocType("Job Card Time Log")
|
jctl = frappe.qb.DocType("Job Card Time Log")
|
||||||
|
|
||||||
@@ -412,13 +416,15 @@ class JobCard(Document):
|
|||||||
.select(jc.name)
|
.select(jc.name)
|
||||||
.where(
|
.where(
|
||||||
(jctl.parent == jc.name)
|
(jctl.parent == jc.name)
|
||||||
& (jc.workstation == self.workstation)
|
|
||||||
& (jctl.employee == employee)
|
& (jctl.employee == employee)
|
||||||
& (jc.docstatus < 1)
|
& (jc.docstatus < 1)
|
||||||
& (jc.name != self.name)
|
& (jc.name != self.name)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if workstation:
|
||||||
|
query = query.where(jc.workstation == workstation)
|
||||||
|
|
||||||
jobs = query.run(as_dict=True)
|
jobs = query.run(as_dict=True)
|
||||||
return [job.get("name") for job in jobs] if jobs else []
|
return [job.get("name") for job in jobs] if jobs else []
|
||||||
|
|
||||||
|
|||||||
@@ -400,5 +400,6 @@ erpnext.patches.v15_0.migrate_checkbox_to_select_for_reconciliation_effect
|
|||||||
erpnext.patches.v15_0.sync_auto_reconcile_config
|
erpnext.patches.v15_0.sync_auto_reconcile_config
|
||||||
execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment")
|
execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment")
|
||||||
erpnext.patches.v14_0.disable_add_row_in_gross_profit
|
erpnext.patches.v14_0.disable_add_row_in_gross_profit
|
||||||
|
erpnext.patches.v14_0.update_posting_datetime
|
||||||
erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference
|
erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference
|
||||||
erpnext.patches.v15_0.recalculate_amount_difference_field
|
erpnext.patches.v15_0.recalculate_amount_difference_field
|
||||||
10
erpnext/patches/v14_0/update_posting_datetime.py
Normal file
10
erpnext/patches/v14_0/update_posting_datetime.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.db.sql(
|
||||||
|
"""
|
||||||
|
UPDATE `tabStock Ledger Entry`
|
||||||
|
SET posting_datetime = timestamp(posting_date, posting_time)
|
||||||
|
"""
|
||||||
|
)
|
||||||
@@ -1,73 +1,29 @@
|
|||||||
import frappe
|
import frappe
|
||||||
|
from frappe.utils import cstr
|
||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
frappe.reload_doc("assets", "doctype", "Asset Depreciation Schedule")
|
|
||||||
frappe.reload_doc("assets", "doctype", "Asset Finance Book")
|
|
||||||
|
|
||||||
assets = get_details_of_draft_or_submitted_depreciable_assets()
|
|
||||||
|
|
||||||
asset_finance_books_map = get_asset_finance_books_map()
|
asset_finance_books_map = get_asset_finance_books_map()
|
||||||
|
|
||||||
asset_depreciation_schedules_map = get_asset_depreciation_schedules_map()
|
asset_depreciation_schedules_map = get_asset_depreciation_schedules_map()
|
||||||
|
|
||||||
for asset in assets:
|
for key, fb_row in asset_finance_books_map.items():
|
||||||
if not asset_depreciation_schedules_map.get(asset.name):
|
depreciation_schedules = asset_depreciation_schedules_map.get(key)
|
||||||
|
if not depreciation_schedules:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
depreciation_schedules = asset_depreciation_schedules_map[asset.name]
|
asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
|
||||||
|
asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(fb_row, fb_row)
|
||||||
|
asset_depr_schedule_doc.flags.ignore_validate = True
|
||||||
|
asset_depr_schedule_doc.insert()
|
||||||
|
|
||||||
for fb_row in asset_finance_books_map[asset.name]:
|
if fb_row.docstatus == 1:
|
||||||
asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
|
frappe.db.set_value(
|
||||||
|
"Asset Depreciation Schedule",
|
||||||
|
asset_depr_schedule_doc.name,
|
||||||
|
{"docstatus": 1, "status": "Active"},
|
||||||
|
)
|
||||||
|
|
||||||
asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(asset, fb_row)
|
update_depreciation_schedules(depreciation_schedules, asset_depr_schedule_doc.name)
|
||||||
|
|
||||||
asset_depr_schedule_doc.insert()
|
|
||||||
|
|
||||||
if asset.docstatus == 1:
|
|
||||||
asset_depr_schedule_doc.submit()
|
|
||||||
|
|
||||||
depreciation_schedules_of_fb_row = [
|
|
||||||
ds for ds in depreciation_schedules if ds["finance_book_id"] == str(fb_row.idx)
|
|
||||||
]
|
|
||||||
|
|
||||||
update_depreciation_schedules(depreciation_schedules_of_fb_row, asset_depr_schedule_doc.name)
|
|
||||||
|
|
||||||
|
|
||||||
def get_details_of_draft_or_submitted_depreciable_assets():
|
|
||||||
asset = frappe.qb.DocType("Asset")
|
|
||||||
|
|
||||||
records = (
|
|
||||||
frappe.qb.from_(asset)
|
|
||||||
.select(
|
|
||||||
asset.name,
|
|
||||||
asset.opening_accumulated_depreciation,
|
|
||||||
asset.gross_purchase_amount,
|
|
||||||
asset.opening_number_of_booked_depreciations,
|
|
||||||
asset.docstatus,
|
|
||||||
)
|
|
||||||
.where(asset.calculate_depreciation == 1)
|
|
||||||
.where(asset.docstatus < 2)
|
|
||||||
).run(as_dict=True)
|
|
||||||
|
|
||||||
return records
|
|
||||||
|
|
||||||
|
|
||||||
def group_records_by_asset_name(records):
|
|
||||||
grouped_dict = {}
|
|
||||||
|
|
||||||
for item in records:
|
|
||||||
key = next(iter(item.keys()))
|
|
||||||
value = item[key]
|
|
||||||
|
|
||||||
if value not in grouped_dict:
|
|
||||||
grouped_dict[value] = []
|
|
||||||
|
|
||||||
del item["asset_name"]
|
|
||||||
|
|
||||||
grouped_dict[value].append(item)
|
|
||||||
|
|
||||||
return grouped_dict
|
|
||||||
|
|
||||||
|
|
||||||
def get_asset_finance_books_map():
|
def get_asset_finance_books_map():
|
||||||
@@ -89,12 +45,20 @@ def get_asset_finance_books_map():
|
|||||||
afb.expected_value_after_useful_life,
|
afb.expected_value_after_useful_life,
|
||||||
afb.daily_prorata_based,
|
afb.daily_prorata_based,
|
||||||
afb.shift_based,
|
afb.shift_based,
|
||||||
|
asset.docstatus,
|
||||||
|
asset.name,
|
||||||
|
asset.opening_accumulated_depreciation,
|
||||||
|
asset.gross_purchase_amount,
|
||||||
|
asset.opening_number_of_booked_depreciations,
|
||||||
)
|
)
|
||||||
.where(asset.docstatus < 2)
|
.where(asset.docstatus < 2)
|
||||||
|
.where(asset.calculate_depreciation == 1)
|
||||||
.orderby(afb.idx)
|
.orderby(afb.idx)
|
||||||
).run(as_dict=True)
|
).run(as_dict=True)
|
||||||
|
|
||||||
asset_finance_books_map = group_records_by_asset_name(records)
|
asset_finance_books_map = frappe._dict()
|
||||||
|
for d in records:
|
||||||
|
asset_finance_books_map.setdefault((d.asset_name, cstr(d.finance_book)), d)
|
||||||
|
|
||||||
return asset_finance_books_map
|
return asset_finance_books_map
|
||||||
|
|
||||||
@@ -110,13 +74,17 @@ def get_asset_depreciation_schedules_map():
|
|||||||
.select(
|
.select(
|
||||||
asset.name.as_("asset_name"),
|
asset.name.as_("asset_name"),
|
||||||
ds.name,
|
ds.name,
|
||||||
|
ds.finance_book,
|
||||||
ds.finance_book_id,
|
ds.finance_book_id,
|
||||||
)
|
)
|
||||||
.where(asset.docstatus < 2)
|
.where(asset.docstatus < 2)
|
||||||
|
.where(asset.calculate_depreciation == 1)
|
||||||
.orderby(ds.idx)
|
.orderby(ds.idx)
|
||||||
).run(as_dict=True)
|
).run(as_dict=True)
|
||||||
|
|
||||||
asset_depreciation_schedules_map = group_records_by_asset_name(records)
|
asset_depreciation_schedules_map = frappe._dict()
|
||||||
|
for d in records:
|
||||||
|
asset_depreciation_schedules_map.setdefault((d.asset_name, cstr(d.finance_book)), []).append(d)
|
||||||
|
|
||||||
return asset_depreciation_schedules_map
|
return asset_depreciation_schedules_map
|
||||||
|
|
||||||
|
|||||||
@@ -526,8 +526,7 @@
|
|||||||
|
|
||||||
> .checkout-btn {
|
> .checkout-btn {
|
||||||
@extend .primary-action;
|
@extend .primary-action;
|
||||||
background-color: var(--blue-200);
|
background-color: var(--control-bg);
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .edit-cart-btn {
|
> .edit-cart-btn {
|
||||||
@@ -769,8 +768,8 @@
|
|||||||
|
|
||||||
.submit-order-btn {
|
.submit-order-btn {
|
||||||
@extend .primary-action;
|
@extend .primary-action;
|
||||||
background-color: var(--primary-color);
|
background-color: var(--btn-primary);
|
||||||
color: white;
|
color: var(--neutral);
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-label {
|
.section-label {
|
||||||
@@ -1087,34 +1086,49 @@
|
|||||||
|
|
||||||
> .item-row-wrapper {
|
> .item-row-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
gap: 2px;
|
||||||
|
flex-direction: column;
|
||||||
padding: var(--padding-sm) var(--padding-md);
|
padding: var(--padding-sm) var(--padding-md);
|
||||||
|
border: 1px solid lightgray;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--bg-light-gray);
|
||||||
|
|
||||||
> .item-name {
|
> .item-row-data {
|
||||||
@extend .nowrap;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-right: var(--margin-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
> .item-qty {
|
|
||||||
font-weight: 500;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .item-rate-disc {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
text-align: right;
|
align-items: center;
|
||||||
margin-left: var(--margin-md);
|
|
||||||
justify-content: flex-end;
|
|
||||||
|
|
||||||
> .item-disc {
|
> .item-name {
|
||||||
color: var(--dark-green-500);
|
@extend .nowrap;
|
||||||
}
|
|
||||||
|
|
||||||
> .item-rate {
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-left: var(--margin-md);
|
margin-right: var(--margin-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .item-qty {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .item-rate-disc {
|
||||||
|
display: flex;
|
||||||
|
text-align: right;
|
||||||
|
margin-left: var(--margin-md);
|
||||||
|
justify-content: flex-end;
|
||||||
|
font-size: small;
|
||||||
|
|
||||||
|
> .item-disc {
|
||||||
|
color: var(--dark-green-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
> .item-rate {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-left: var(--margin-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .item-row-refund {
|
||||||
|
font-size: x-small;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1127,6 +1141,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .order-summary-container {
|
||||||
|
display: flex;
|
||||||
|
background: white;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
> .summary-btns {
|
> .summary-btns {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -353,6 +353,26 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
|
|||||||
);
|
);
|
||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currency() {
|
||||||
|
super.currency();
|
||||||
|
let me = this;
|
||||||
|
const company_currency = this.get_company_currency();
|
||||||
|
if (this.frm.doc.currency && this.frm.doc.currency !== company_currency) {
|
||||||
|
this.get_exchange_rate(
|
||||||
|
this.frm.doc.transaction_date,
|
||||||
|
this.frm.doc.currency,
|
||||||
|
company_currency,
|
||||||
|
function (exchange_rate) {
|
||||||
|
if (exchange_rate != me.frm.doc.conversion_rate) {
|
||||||
|
me.set_margin_amount_based_on_currency(exchange_rate);
|
||||||
|
me.set_actual_charges_based_on_currency(exchange_rate);
|
||||||
|
me.frm.set_value("conversion_rate", exchange_rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
cur_frm.script_manager.make(erpnext.selling.QuotationController);
|
cur_frm.script_manager.make(erpnext.selling.QuotationController);
|
||||||
|
|||||||
@@ -107,7 +107,11 @@
|
|||||||
"purchase_order",
|
"purchase_order",
|
||||||
"column_break_89",
|
"column_break_89",
|
||||||
"material_request_item",
|
"material_request_item",
|
||||||
"purchase_order_item"
|
"purchase_order_item",
|
||||||
|
"accounting_dimensions_section",
|
||||||
|
"cost_center",
|
||||||
|
"column_break_ihdh",
|
||||||
|
"project"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -932,12 +936,42 @@
|
|||||||
"fieldname": "available_quantity_section",
|
"fieldname": "available_quantity_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Available Quantity"
|
"label": "Available Quantity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "accounting_dimensions_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Accounting Dimensions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"default": ":Company",
|
||||||
|
"fieldname": "cost_center",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Cost Center",
|
||||||
|
"options": "Cost Center",
|
||||||
|
"print_hide": 1,
|
||||||
|
"print_width": "120px",
|
||||||
|
"reqd": 1,
|
||||||
|
"width": "120px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_ihdh",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Project",
|
||||||
|
"options": "Project",
|
||||||
|
"search_index": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-01-09 15:34:47.768457",
|
"modified": "2025-02-06 13:29:24.619850",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Sales Order Item",
|
"name": "Sales Order Item",
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class SalesOrderItem(Document):
|
|||||||
brand: DF.Link | None
|
brand: DF.Link | None
|
||||||
company_total_stock: DF.Float
|
company_total_stock: DF.Float
|
||||||
conversion_factor: DF.Float
|
conversion_factor: DF.Float
|
||||||
|
cost_center: DF.Link
|
||||||
customer_item_code: DF.Data | None
|
customer_item_code: DF.Data | None
|
||||||
delivered_by_supplier: DF.Check
|
delivered_by_supplier: DF.Check
|
||||||
delivered_qty: DF.Float
|
delivered_qty: DF.Float
|
||||||
@@ -69,6 +70,7 @@ class SalesOrderItem(Document):
|
|||||||
pricing_rules: DF.SmallText | None
|
pricing_rules: DF.SmallText | None
|
||||||
produced_qty: DF.Float
|
produced_qty: DF.Float
|
||||||
production_plan_qty: DF.Float
|
production_plan_qty: DF.Float
|
||||||
|
project: DF.Link | None
|
||||||
projected_qty: DF.Float
|
projected_qty: DF.Float
|
||||||
purchase_order: DF.Link | None
|
purchase_order: DF.Link | None
|
||||||
purchase_order_item: DF.Data | None
|
purchase_order_item: DF.Data | None
|
||||||
|
|||||||
@@ -459,6 +459,8 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
() => this.make_return_invoice(doc),
|
() => this.make_return_invoice(doc),
|
||||||
() => this.cart.load_invoice(),
|
() => this.cart.load_invoice(),
|
||||||
() => this.item_selector.toggle_component(true),
|
() => this.item_selector.toggle_component(true),
|
||||||
|
() => this.item_selector.resize_selector(false),
|
||||||
|
() => this.item_details.toggle_component(false),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -469,6 +471,8 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
() => this.frm.call("reset_mode_of_payments"),
|
() => this.frm.call("reset_mode_of_payments"),
|
||||||
() => this.cart.load_invoice(),
|
() => this.cart.load_invoice(),
|
||||||
() => this.item_selector.toggle_component(true),
|
() => this.item_selector.toggle_component(true),
|
||||||
|
() => this.item_selector.resize_selector(false),
|
||||||
|
() => this.item_details.toggle_component(false),
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
delete_order: (name) => {
|
delete_order: (name) => {
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ erpnext.PointOfSale.ItemCart = class {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.$component.on("click", ".checkout-btn", async function () {
|
this.$component.on("click", ".checkout-btn", async function () {
|
||||||
if ($(this).attr("style").indexOf("--primary-color") == -1) return;
|
if ($(this).attr("style").indexOf("--btn-primary") == -1) return;
|
||||||
|
|
||||||
await me.events.checkout();
|
await me.events.checkout();
|
||||||
me.toggle_checkout_btn(false);
|
me.toggle_checkout_btn(false);
|
||||||
@@ -702,12 +702,14 @@ erpnext.PointOfSale.ItemCart = class {
|
|||||||
if (toggle) {
|
if (toggle) {
|
||||||
this.$add_discount_elem.css("display", "flex");
|
this.$add_discount_elem.css("display", "flex");
|
||||||
this.$cart_container.find(".checkout-btn").css({
|
this.$cart_container.find(".checkout-btn").css({
|
||||||
"background-color": "var(--primary-color)",
|
"background-color": "var(--btn-primary)",
|
||||||
|
color: "var(--neutral)",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.$add_discount_elem.css("display", "none");
|
this.$add_discount_elem.css("display", "none");
|
||||||
this.$cart_container.find(".checkout-btn").css({
|
this.$cart_container.find(".checkout-btn").css({
|
||||||
"background-color": "var(--gray-200)",
|
"background-color": "var(--control-bg)",
|
||||||
|
color: "",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
<div class="abs-container">
|
<div class="abs-container">
|
||||||
<div class="upper-section"></div>
|
<div class="upper-section"></div>
|
||||||
<div class="label">${__("Items")}</div>
|
<div class="label">${__("Items")}</div>
|
||||||
<div class="items-container summary-container"></div>
|
<div class="items-container summary-container order-summary-container"></div>
|
||||||
<div class="label">${__("Totals")}</div>
|
<div class="label">${__("Totals")}</div>
|
||||||
<div class="totals-container summary-container"></div>
|
<div class="totals-container summary-container"></div>
|
||||||
<div class="label">${__("Payments")}</div>
|
<div class="label">${__("Payments")}</div>
|
||||||
@@ -90,12 +90,18 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get_item_html(doc, item_data) {
|
async get_item_html(doc, item_data) {
|
||||||
|
const item_refund_data = doc.is_return || doc.docstatus === 0 ? "" : await get_returned_qty();
|
||||||
|
|
||||||
return `<div class="item-row-wrapper">
|
return `<div class="item-row-wrapper">
|
||||||
|
<div class="item-row-data">
|
||||||
<div class="item-name">${item_data.item_name}</div>
|
<div class="item-name">${item_data.item_name}</div>
|
||||||
<div class="item-qty">${item_data.qty || 0} ${item_data.uom}</div>
|
<div class="item-qty">${item_data.qty || 0} ${item_data.uom}</div>
|
||||||
<div class="item-rate-disc">${get_rate_discount_html()}</div>
|
<div class="item-rate-disc">${get_rate_discount_html()}</div>
|
||||||
</div>`;
|
</div>
|
||||||
|
|
||||||
|
${item_refund_data}
|
||||||
|
</div>`;
|
||||||
|
|
||||||
function get_rate_discount_html() {
|
function get_rate_discount_html() {
|
||||||
if (item_data.rate && item_data.price_list_rate && item_data.rate !== item_data.price_list_rate) {
|
if (item_data.rate && item_data.price_list_rate && item_data.rate !== item_data.price_list_rate) {
|
||||||
@@ -108,6 +114,25 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
)}</div>`;
|
)}</div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function get_returned_qty() {
|
||||||
|
const r = await frappe.call({
|
||||||
|
method: "erpnext.controllers.sales_and_purchase_return.get_pos_invoice_item_returned_qty",
|
||||||
|
args: {
|
||||||
|
pos_invoice: doc.name,
|
||||||
|
customer: doc.customer,
|
||||||
|
item_row_name: item_data.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!r.message.qty) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<div class="item-row-refund">
|
||||||
|
<strong>${r.message.qty}</strong> ${__("Returned")}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get_discount_html(doc) {
|
get_discount_html(doc) {
|
||||||
@@ -166,7 +191,16 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bind_events() {
|
bind_events() {
|
||||||
this.$summary_container.on("click", ".return-btn", () => {
|
this.$summary_container.on("click", ".return-btn", async () => {
|
||||||
|
const r = await this.is_pos_invoice_returnable(this.doc.name);
|
||||||
|
if (!r) {
|
||||||
|
frappe.msgprint({
|
||||||
|
title: __("Invalid Return"),
|
||||||
|
indicator: "orange",
|
||||||
|
message: __("All the items have been already returned."),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.events.process_return(this.doc.name);
|
this.events.process_return(this.doc.name);
|
||||||
this.toggle_component(false);
|
this.toggle_component(false);
|
||||||
this.$component.find(".no-summary-placeholder").css("display", "flex");
|
this.$component.find(".no-summary-placeholder").css("display", "flex");
|
||||||
@@ -370,13 +404,13 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
attach_items_info(doc) {
|
async attach_items_info(doc) {
|
||||||
this.$items_container.html("");
|
this.$items_container.html("");
|
||||||
doc.items.forEach((item) => {
|
for (const item of doc.items) {
|
||||||
const item_dom = this.get_item_html(doc, item);
|
const item_dom = await this.get_item_html(doc, item);
|
||||||
this.$items_container.append(item_dom);
|
this.$items_container.append(item_dom);
|
||||||
this.set_dynamic_rate_header_width();
|
this.set_dynamic_rate_header_width();
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set_dynamic_rate_header_width() {
|
set_dynamic_rate_header_width() {
|
||||||
@@ -438,4 +472,14 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
this.print_receipt();
|
this.print_receipt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async is_pos_invoice_returnable(invoice) {
|
||||||
|
const r = await frappe.call({
|
||||||
|
method: "erpnext.controllers.sales_and_purchase_return.is_pos_invoice_returnable",
|
||||||
|
args: {
|
||||||
|
pos_invoice: invoice,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return r.message;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"doctype": "Workspace",
|
"doctype": "Workspace",
|
||||||
"for_user": "",
|
"for_user": "",
|
||||||
"hide_custom": 0,
|
"hide_custom": 0,
|
||||||
"icon": "getting-started",
|
"icon": "home",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_hidden": 0,
|
"is_hidden": 0,
|
||||||
"label": "Home",
|
"label": "Home",
|
||||||
@@ -232,7 +232,7 @@
|
|||||||
"type": "Link"
|
"type": "Link"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-05-24 14:47:18.765388",
|
"modified": "2025-02-17 10:55:02.213683",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Home",
|
"name": "Home",
|
||||||
|
|||||||
@@ -790,6 +790,48 @@ class TestDeliveryNote(IntegrationTestCase):
|
|||||||
{"warehouse": "_Test Warehouse - _TC"},
|
{"warehouse": "_Test Warehouse - _TC"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_delivery_note_internal_transfer_serial_no_status(self):
|
||||||
|
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
|
||||||
|
|
||||||
|
item = make_item(
|
||||||
|
"_Test Item for Internal Transfer With Serial No Status",
|
||||||
|
properties={"has_serial_no": 1, "is_stock_item": 1, "serial_no_series": "INT-SN-.####"},
|
||||||
|
).name
|
||||||
|
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
target = "Stores - _TC"
|
||||||
|
company = "_Test Company"
|
||||||
|
customer = create_internal_customer(represents_company=company)
|
||||||
|
rate = 42
|
||||||
|
|
||||||
|
se = make_stock_entry(target=warehouse, qty=5, basic_rate=rate, item_code=item)
|
||||||
|
serial_nos = get_serial_nos_from_bundle(se.get("items")[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
|
dn = create_delivery_note(
|
||||||
|
item_code=item,
|
||||||
|
company=company,
|
||||||
|
customer=customer,
|
||||||
|
qty=5,
|
||||||
|
rate=500,
|
||||||
|
warehouse=warehouse,
|
||||||
|
target_warehouse=target,
|
||||||
|
ignore_pricing_rule=0,
|
||||||
|
use_serial_batch_fields=1,
|
||||||
|
serial_no="\n".join(serial_nos),
|
||||||
|
)
|
||||||
|
|
||||||
|
for serial_no in serial_nos:
|
||||||
|
sn = frappe.db.get_value("Serial No", serial_no, ["status", "warehouse"], as_dict=1)
|
||||||
|
self.assertEqual(sn.status, "Active")
|
||||||
|
self.assertEqual(sn.warehouse, target)
|
||||||
|
|
||||||
|
dn.cancel()
|
||||||
|
|
||||||
|
for serial_no in serial_nos:
|
||||||
|
sn = frappe.db.get_value("Serial No", serial_no, ["status", "warehouse"], as_dict=1)
|
||||||
|
self.assertEqual(sn.status, "Active")
|
||||||
|
self.assertEqual(sn.warehouse, warehouse)
|
||||||
|
|
||||||
def test_delivery_of_bundled_items_to_target_warehouse(self):
|
def test_delivery_of_bundled_items_to_target_warehouse(self):
|
||||||
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
|
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
"allow_guest_to_view": 1,
|
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"allow_rename": 1,
|
"allow_rename": 1,
|
||||||
"autoname": "field:item_code",
|
"autoname": "field:item_code",
|
||||||
@@ -888,10 +887,9 @@
|
|||||||
"icon": "fa fa-tag",
|
"icon": "fa fa-tag",
|
||||||
"idx": 2,
|
"idx": 2,
|
||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"index_web_pages_for_search": 1,
|
|
||||||
"links": [],
|
"links": [],
|
||||||
"make_attachments_public": 1,
|
"make_attachments_public": 1,
|
||||||
"modified": "2024-04-30 13:46:39.098753",
|
"modified": "2025-02-03 23:43:57.253667",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Item",
|
"name": "Item",
|
||||||
|
|||||||
@@ -202,7 +202,8 @@
|
|||||||
"oldfieldname": "parent_detail_docname",
|
"oldfieldname": "parent_detail_docname",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "batch_no",
|
"depends_on": "batch_no",
|
||||||
@@ -295,7 +296,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-27 13:10:07.966447",
|
"modified": "2025-02-18 13:06:02.789654",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Packed Item",
|
"name": "Packed Item",
|
||||||
|
|||||||
@@ -1251,6 +1251,7 @@ class TestPickList(IntegrationTestCase):
|
|||||||
"is_recursive": 1,
|
"is_recursive": 1,
|
||||||
"recurse_for": 2,
|
"recurse_for": 2,
|
||||||
"free_qty": 1,
|
"free_qty": 1,
|
||||||
|
"dont_enforce_free_item_qty": 0,
|
||||||
"company": "_Test Company",
|
"company": "_Test Company",
|
||||||
"customer": "_Test Customer",
|
"customer": "_Test Customer",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,9 @@
|
|||||||
"verified_by",
|
"verified_by",
|
||||||
"column_break_17",
|
"column_break_17",
|
||||||
"remarks",
|
"remarks",
|
||||||
"amended_from"
|
"amended_from",
|
||||||
|
"print_settings_section",
|
||||||
|
"letter_head"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -255,6 +257,20 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"options": "Company"
|
"options": "Company"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "print_settings_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Print Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"fetch_from": "company.default_letter_head",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
|
"fieldname": "letter_head",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Letter Head",
|
||||||
|
"options": "Letter Head"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-search",
|
"icon": "fa fa-search",
|
||||||
@@ -262,7 +278,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-01-16 17:00:48.774532",
|
"modified": "2025-02-17 13:20:17.583094",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Quality Inspection",
|
"name": "Quality Inspection",
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class QualityInspection(Document):
|
|||||||
item_code: DF.Link
|
item_code: DF.Link
|
||||||
item_name: DF.Data | None
|
item_name: DF.Data | None
|
||||||
item_serial_no: DF.Link | None
|
item_serial_no: DF.Link | None
|
||||||
|
letter_head: DF.Link | None
|
||||||
manual_inspection: DF.Check
|
manual_inspection: DF.Check
|
||||||
naming_series: DF.Literal["MAT-QA-.YYYY.-"]
|
naming_series: DF.Literal["MAT-QA-.YYYY.-"]
|
||||||
quality_inspection_template: DF.Link | None
|
quality_inspection_template: DF.Link | None
|
||||||
|
|||||||
@@ -134,6 +134,13 @@ frappe.ui.form.on("Serial and Batch Bundle", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
toggle_fields(frm) {
|
toggle_fields(frm) {
|
||||||
|
let show_naming_series_field =
|
||||||
|
frappe.user_defaults.set_serial_and_batch_bundle_naming_based_on_naming_series;
|
||||||
|
frm.toggle_display("naming_series", cint(show_naming_series_field));
|
||||||
|
frm.toggle_reqd("naming_series", cint(show_naming_series_field));
|
||||||
|
|
||||||
|
frm.toggle_display("naming_series", frm.doc.__islocal ? true : false);
|
||||||
|
|
||||||
if (frm.doc.has_serial_no) {
|
if (frm.doc.has_serial_no) {
|
||||||
frm.doc.entries.forEach((row) => {
|
frm.doc.entries.forEach((row) => {
|
||||||
if (Math.abs(row.qty) !== 1) {
|
if (Math.abs(row.qty) !== 1) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"item_details_tab",
|
"item_details_tab",
|
||||||
|
"naming_series",
|
||||||
"company",
|
"company",
|
||||||
"item_name",
|
"item_name",
|
||||||
"has_serial_no",
|
"has_serial_no",
|
||||||
@@ -242,12 +243,20 @@
|
|||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Returned Against",
|
"label": "Returned Against",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "SABB-.########",
|
||||||
|
"fieldname": "naming_series",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Naming Series",
|
||||||
|
"options": "\nSABB-.########",
|
||||||
|
"set_only_once": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-02-12 09:53:32.090309",
|
"modified": "2025-02-17 16:22:36.056205",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Serial and Batch Bundle",
|
"name": "Serial and Batch Bundle",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from collections import Counter, defaultdict
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, _dict, bold
|
from frappe import _, _dict, bold
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.model.naming import make_autoname
|
||||||
from frappe.query_builder.functions import CombineDatetime, Sum
|
from frappe.query_builder.functions import CombineDatetime, Sum
|
||||||
from frappe.utils import (
|
from frappe.utils import (
|
||||||
add_days,
|
add_days,
|
||||||
@@ -68,6 +69,7 @@ class SerialandBatchBundle(Document):
|
|||||||
item_code: DF.Link
|
item_code: DF.Link
|
||||||
item_group: DF.Link | None
|
item_group: DF.Link | None
|
||||||
item_name: DF.Data | None
|
item_name: DF.Data | None
|
||||||
|
naming_series: DF.Literal["", "SABB-.########"]
|
||||||
posting_date: DF.Date | None
|
posting_date: DF.Date | None
|
||||||
posting_time: DF.Time | None
|
posting_time: DF.Time | None
|
||||||
returned_against: DF.Data | None
|
returned_against: DF.Data | None
|
||||||
@@ -80,6 +82,24 @@ class SerialandBatchBundle(Document):
|
|||||||
warehouse: DF.Link | None
|
warehouse: DF.Link | None
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
|
def autoname(self):
|
||||||
|
if frappe.db.get_single_value(
|
||||||
|
"Stock Settings", "set_serial_and_batch_bundle_naming_based_on_naming_series"
|
||||||
|
):
|
||||||
|
if not self.naming_series:
|
||||||
|
frappe.throw(_("Naming Series is mandatory"))
|
||||||
|
|
||||||
|
naming_series = self.naming_series
|
||||||
|
if "#" not in naming_series:
|
||||||
|
naming_series += ".#####"
|
||||||
|
|
||||||
|
self.name = make_autoname(self.naming_series)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.name = frappe.generate_hash(length=20)
|
||||||
|
except frappe.DuplicateEntryError:
|
||||||
|
self.autoname()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if self.docstatus == 1 and self.voucher_detail_no:
|
if self.docstatus == 1 and self.voucher_detail_no:
|
||||||
self.validate_voucher_detail_no()
|
self.validate_voucher_detail_no()
|
||||||
|
|||||||
@@ -26,6 +26,67 @@ class UnitTestSerialAndBatchBundle(UnitTestCase):
|
|||||||
|
|
||||||
|
|
||||||
class TestSerialandBatchBundle(IntegrationTestCase):
|
class TestSerialandBatchBundle(IntegrationTestCase):
|
||||||
|
def test_naming_for_sabb(self):
|
||||||
|
frappe.db.set_single_value(
|
||||||
|
"Stock Settings", "set_serial_and_batch_bundle_naming_based_on_naming_series", 1
|
||||||
|
)
|
||||||
|
|
||||||
|
serial_item_code = "New Serial No Valuation 11"
|
||||||
|
make_item(
|
||||||
|
serial_item_code,
|
||||||
|
{
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"serial_no_series": "TEST-A-SER-VAL-.#####",
|
||||||
|
"is_stock_item": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
for sn in ["TEST-A-SER-VAL-00001", "TEST-A-SER-VAL-00002"]:
|
||||||
|
if not frappe.db.exists("Serial No", sn):
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Serial No",
|
||||||
|
"serial_no": sn,
|
||||||
|
"item_code": serial_item_code,
|
||||||
|
}
|
||||||
|
).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
bundle_doc = make_serial_batch_bundle(
|
||||||
|
{
|
||||||
|
"item_code": serial_item_code,
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"voucher_type": "Stock Entry",
|
||||||
|
"posting_date": today(),
|
||||||
|
"posting_time": nowtime(),
|
||||||
|
"qty": 10,
|
||||||
|
"serial_nos": ["TEST-A-SER-VAL-00001", "TEST-A-SER-VAL-00002"],
|
||||||
|
"type_of_transaction": "Inward",
|
||||||
|
"do_not_submit": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(bundle_doc.name.startswith("SABB-"))
|
||||||
|
|
||||||
|
frappe.db.set_single_value(
|
||||||
|
"Stock Settings", "set_serial_and_batch_bundle_naming_based_on_naming_series", 0
|
||||||
|
)
|
||||||
|
|
||||||
|
bundle_doc = make_serial_batch_bundle(
|
||||||
|
{
|
||||||
|
"item_code": serial_item_code,
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"voucher_type": "Stock Entry",
|
||||||
|
"posting_date": today(),
|
||||||
|
"posting_time": nowtime(),
|
||||||
|
"qty": 10,
|
||||||
|
"serial_nos": ["TEST-A-SER-VAL-00001", "TEST-A-SER-VAL-00002"],
|
||||||
|
"type_of_transaction": "Inward",
|
||||||
|
"do_not_submit": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertFalse(bundle_doc.name.startswith("SABB-"))
|
||||||
|
|
||||||
def test_inward_outward_serial_valuation(self):
|
def test_inward_outward_serial_valuation(self):
|
||||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
|
|||||||
@@ -57,6 +57,8 @@
|
|||||||
"use_serial_batch_fields",
|
"use_serial_batch_fields",
|
||||||
"do_not_update_serial_batch_on_creation_of_auto_bundle",
|
"do_not_update_serial_batch_on_creation_of_auto_bundle",
|
||||||
"allow_existing_serial_no",
|
"allow_existing_serial_no",
|
||||||
|
"serial_and_batch_bundle_section",
|
||||||
|
"set_serial_and_batch_bundle_naming_based_on_naming_series",
|
||||||
"stock_planning_tab",
|
"stock_planning_tab",
|
||||||
"auto_material_request",
|
"auto_material_request",
|
||||||
"auto_indent",
|
"auto_indent",
|
||||||
@@ -475,6 +477,17 @@
|
|||||||
"fieldname": "auto_reserve_stock",
|
"fieldname": "auto_reserve_stock",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Auto Reserve Stock"
|
"label": "Auto Reserve Stock"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "serial_and_batch_bundle_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Serial and Batch Bundle"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "set_serial_and_batch_bundle_naming_based_on_naming_series",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Set Serial and Batch Bundle Naming Based on Naming Series"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@@ -482,7 +495,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-12-10 17:52:36.030456",
|
"modified": "2025-02-17 13:36:36.177743",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Settings",
|
"name": "Stock Settings",
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class StockSettings(Document):
|
|||||||
role_allowed_to_create_edit_back_dated_transactions: DF.Link | None
|
role_allowed_to_create_edit_back_dated_transactions: DF.Link | None
|
||||||
role_allowed_to_over_deliver_receive: DF.Link | None
|
role_allowed_to_over_deliver_receive: DF.Link | None
|
||||||
sample_retention_warehouse: DF.Link | None
|
sample_retention_warehouse: DF.Link | None
|
||||||
|
set_serial_and_batch_bundle_naming_based_on_naming_series: DF.Check
|
||||||
show_barcode_field: DF.Check
|
show_barcode_field: DF.Check
|
||||||
stock_auth_role: DF.Link | None
|
stock_auth_role: DF.Link | None
|
||||||
stock_frozen_upto: DF.Date | None
|
stock_frozen_upto: DF.Date | None
|
||||||
@@ -76,6 +77,7 @@ class StockSettings(Document):
|
|||||||
"default_warehouse",
|
"default_warehouse",
|
||||||
"set_qty_in_transactions_based_on_serial_no_input",
|
"set_qty_in_transactions_based_on_serial_no_input",
|
||||||
"use_serial_batch_fields",
|
"use_serial_batch_fields",
|
||||||
|
"set_serial_and_batch_bundle_naming_based_on_naming_series",
|
||||||
]:
|
]:
|
||||||
frappe.db.set_default(key, self.get(key, ""))
|
frappe.db.set_default(key, self.get(key, ""))
|
||||||
|
|
||||||
|
|||||||
@@ -663,7 +663,7 @@ def get_combine_datetime(posting_date, posting_time):
|
|||||||
if isinstance(posting_time, datetime.timedelta):
|
if isinstance(posting_time, datetime.timedelta):
|
||||||
posting_time = (datetime.datetime.min + posting_time).time()
|
posting_time = (datetime.datetime.min + posting_time).time()
|
||||||
|
|
||||||
return datetime.datetime.combine(posting_date, posting_time).replace(microsecond=0)
|
return datetime.datetime.combine(posting_date, posting_time)
|
||||||
|
|
||||||
|
|
||||||
@frappe.request_cache
|
@frappe.request_cache
|
||||||
|
|||||||
Reference in New Issue
Block a user