Merge remote-tracking branch 'origin/develop' into feat/so-po-advance-payment-status

This commit is contained in:
David Arnold 2023-12-20 12:47:48 +01:00
commit f73685f4f6
No known key found for this signature in database
GPG Key ID: AB15A6AF1101390D
142 changed files with 2182 additions and 742 deletions

View File

@ -28,4 +28,7 @@ b147b85e6ac19a9220cd1e2958a6ebd99373283a
494bd9ef78313436f0424b918f200dab8fc7c20b 494bd9ef78313436f0424b918f200dab8fc7c20b
# bulk format python code with black # bulk format python code with black
baec607ff5905b1c67531096a9cf50ec7ff00a5d baec607ff5905b1c67531096a9cf50ec7ff00a5d
# bulk refactor with sourcery
eb9ee3f79b94e594fc6dfa4f6514580e125eee8c

View File

@ -36,7 +36,7 @@ def get_default_cost_center(company):
if not frappe.flags.company_cost_center: if not frappe.flags.company_cost_center:
frappe.flags.company_cost_center = {} frappe.flags.company_cost_center = {}
if not company in frappe.flags.company_cost_center: if company not in frappe.flags.company_cost_center:
frappe.flags.company_cost_center[company] = frappe.get_cached_value( frappe.flags.company_cost_center[company] = frappe.get_cached_value(
"Company", company, "cost_center" "Company", company, "cost_center"
) )
@ -47,7 +47,7 @@ def get_company_currency(company):
"""Returns the default company currency""" """Returns the default company currency"""
if not frappe.flags.company_currency: if not frappe.flags.company_currency:
frappe.flags.company_currency = {} frappe.flags.company_currency = {}
if not company in frappe.flags.company_currency: if company not in frappe.flags.company_currency:
frappe.flags.company_currency[company] = frappe.db.get_value( frappe.flags.company_currency[company] = frappe.db.get_value(
"Company", company, "default_currency", cache=True "Company", company, "default_currency", cache=True
) )
@ -81,7 +81,7 @@ def is_perpetual_inventory_enabled(company):
if not hasattr(frappe.local, "enable_perpetual_inventory"): if not hasattr(frappe.local, "enable_perpetual_inventory"):
frappe.local.enable_perpetual_inventory = {} frappe.local.enable_perpetual_inventory = {}
if not company in frappe.local.enable_perpetual_inventory: if company not in frappe.local.enable_perpetual_inventory:
frappe.local.enable_perpetual_inventory[company] = ( frappe.local.enable_perpetual_inventory[company] = (
frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0 frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0
) )
@ -96,7 +96,7 @@ def get_default_finance_book(company=None):
if not hasattr(frappe.local, "default_finance_book"): if not hasattr(frappe.local, "default_finance_book"):
frappe.local.default_finance_book = {} frappe.local.default_finance_book = {}
if not company in frappe.local.default_finance_book: if company not in frappe.local.default_finance_book:
frappe.local.default_finance_book[company] = frappe.get_cached_value( frappe.local.default_finance_book[company] = frappe.get_cached_value(
"Company", company, "default_finance_book" "Company", company, "default_finance_book"
) )
@ -108,7 +108,7 @@ def get_party_account_type(party_type):
if not hasattr(frappe.local, "party_account_types"): if not hasattr(frappe.local, "party_account_types"):
frappe.local.party_account_types = {} frappe.local.party_account_types = {}
if not party_type in frappe.local.party_account_types: if party_type not in frappe.local.party_account_types:
frappe.local.party_account_types[party_type] = ( frappe.local.party_account_types[party_type] = (
frappe.db.get_value("Party Type", party_type, "account_type") or "" frappe.db.get_value("Party Type", party_type, "account_type") or ""
) )

View File

@ -232,7 +232,7 @@ def calculate_monthly_amount(
if amount + already_booked_amount_in_account_currency > item.net_amount: if amount + already_booked_amount_in_account_currency > item.net_amount:
amount = item.net_amount - already_booked_amount_in_account_currency amount = item.net_amount - already_booked_amount_in_account_currency
if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date): if get_first_day(start_date) != start_date or get_last_day(end_date) != end_date:
partial_month = flt(date_diff(end_date, start_date)) / flt( partial_month = flt(date_diff(end_date, start_date)) / flt(
date_diff(get_last_day(end_date), get_first_day(start_date)) date_diff(get_last_day(end_date), get_first_day(start_date))
) )
@ -358,9 +358,11 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
account_currency = get_account_currency(item.expense_account or item.income_account) account_currency = get_account_currency(item.expense_account or item.income_account)
if doc.doctype == "Sales Invoice": if doc.doctype == "Sales Invoice":
against_type = "Customer"
against, project = doc.customer, doc.project against, project = doc.customer, doc.project
credit_account, debit_account = item.income_account, item.deferred_revenue_account credit_account, debit_account = item.income_account, item.deferred_revenue_account
else: else:
against_type = "Supplier"
against, project = doc.supplier, item.project against, project = doc.supplier, item.project
credit_account, debit_account = item.deferred_expense_account, item.expense_account credit_account, debit_account = item.deferred_expense_account, item.expense_account
@ -413,6 +415,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
doc, doc,
credit_account, credit_account,
debit_account, debit_account,
against_type,
against, against,
amount, amount,
base_amount, base_amount,
@ -494,6 +497,7 @@ def make_gl_entries(
doc, doc,
credit_account, credit_account,
debit_account, debit_account,
against_type,
against, against,
amount, amount,
base_amount, base_amount,
@ -515,7 +519,9 @@ def make_gl_entries(
doc.get_gl_dict( doc.get_gl_dict(
{ {
"account": credit_account, "account": credit_account,
"against_type": against_type,
"against": against, "against": against,
"against_link": against,
"credit": base_amount, "credit": base_amount,
"credit_in_account_currency": amount, "credit_in_account_currency": amount,
"cost_center": cost_center, "cost_center": cost_center,
@ -534,7 +540,9 @@ def make_gl_entries(
doc.get_gl_dict( doc.get_gl_dict(
{ {
"account": debit_account, "account": debit_account,
"against_type": against_type,
"against": against, "against": against,
"against_link": against,
"debit": base_amount, "debit": base_amount,
"debit_in_account_currency": amount, "debit_in_account_currency": amount,
"cost_center": cost_center, "cost_center": cost_center,

View File

@ -33,7 +33,9 @@
}, },
"Stocks": { "Stocks": {
"Mati\u00e8res premi\u00e8res": {}, "Mati\u00e8res premi\u00e8res": {},
"Stock de produits fini": {}, "Stock de produits fini": {
"account_type": "Stock"
},
"Stock exp\u00e9di\u00e9 non-factur\u00e9": {}, "Stock exp\u00e9di\u00e9 non-factur\u00e9": {},
"Travaux en cours": {}, "Travaux en cours": {},
"account_type": "Stock" "account_type": "Stock"
@ -395,9 +397,11 @@
}, },
"Produits": { "Produits": {
"Revenus de ventes": { "Revenus de ventes": {
" Escomptes de volume sur ventes": {}, "Escomptes de volume sur ventes": {},
"Autres produits d'exploitation": {}, "Autres produits d'exploitation": {},
"Ventes": {}, "Ventes": {
"account_type": "Income Account"
},
"Ventes avec des provinces harmonis\u00e9es": {}, "Ventes avec des provinces harmonis\u00e9es": {},
"Ventes avec des provinces non-harmonis\u00e9es": {}, "Ventes avec des provinces non-harmonis\u00e9es": {},
"Ventes \u00e0 l'\u00e9tranger": {} "Ventes \u00e0 l'\u00e9tranger": {}

View File

@ -53,8 +53,13 @@
}, },
"II. Forderungen und sonstige Vermögensgegenstände": { "II. Forderungen und sonstige Vermögensgegenstände": {
"is_group": 1, "is_group": 1,
"Ford. a. Lieferungen und Leistungen": { "Forderungen aus Lieferungen und Leistungen mit Kontokorrent": {
"account_number": "1400", "account_number": "1400",
"account_type": "Receivable",
"is_group": 1
},
"Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "1410",
"account_type": "Receivable" "account_type": "Receivable"
}, },
"Durchlaufende Posten": { "Durchlaufende Posten": {
@ -180,8 +185,13 @@
}, },
"IV. Verbindlichkeiten aus Lieferungen und Leistungen": { "IV. Verbindlichkeiten aus Lieferungen und Leistungen": {
"is_group": 1, "is_group": 1,
"Verbindlichkeiten aus Lieferungen u. Leistungen": { "Verbindlichkeiten aus Lieferungen und Leistungen mit Kontokorrent": {
"account_number": "1600", "account_number": "1600",
"account_type": "Payable",
"is_group": 1
},
"Verbindlichkeiten aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "1610",
"account_type": "Payable" "account_type": "Payable"
} }
}, },

View File

@ -407,13 +407,10 @@
"Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": { "Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": {
"account_number": "9960" "account_number": "9960"
}, },
"Debitoren": { "Forderungen aus Lieferungen und Leistungen mit Kontokorrent": {
"is_group": 1,
"account_number": "10000"
},
"Forderungen aus Lieferungen und Leistungen": {
"account_number": "1200", "account_number": "1200",
"account_type": "Receivable" "account_type": "Receivable",
"is_group": 1
}, },
"Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": { "Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "1210" "account_number": "1210"
@ -1138,18 +1135,15 @@
"Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": { "Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": {
"account_number": "9964" "account_number": "9964"
}, },
"Kreditoren": { "Verb. aus Lieferungen und Leistungen mit Kontokorrent": {
"account_number": "70000", "account_number": "3300",
"account_type": "Payable",
"is_group": 1, "is_group": 1,
"Wareneingangs-­Verrechnungskonto" : { "Wareneingangs-Verrechnungskonto" : {
"account_number": "70001", "account_number": "70001",
"account_type": "Stock Received But Not Billed" "account_type": "Stock Received But Not Billed"
} }
}, },
"Verb. aus Lieferungen und Leistungen": {
"account_number": "3300",
"account_type": "Payable"
},
"Verb. aus Lieferungen und Leistungen ohne Kontokorrent": { "Verb. aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "3310" "account_number": "3310"
}, },

View File

@ -44,7 +44,7 @@ class ExchangeRateRevaluation(Document):
self.set_total_gain_loss() self.set_total_gain_loss()
def validate_rounding_loss_allowance(self): def validate_rounding_loss_allowance(self):
if not (self.rounding_loss_allowance >= 0 and self.rounding_loss_allowance < 1): if self.rounding_loss_allowance < 0 or self.rounding_loss_allowance >= 1:
frappe.throw(_("Rounding Loss Allowance should be between 0 and 1")) frappe.throw(_("Rounding Loss Allowance should be between 0 and 1"))
def set_total_gain_loss(self): def set_total_gain_loss(self):

View File

@ -17,7 +17,9 @@
"account_currency", "account_currency",
"debit_in_account_currency", "debit_in_account_currency",
"credit_in_account_currency", "credit_in_account_currency",
"against_type",
"against", "against",
"against_link",
"against_voucher_type", "against_voucher_type",
"against_voucher", "against_voucher",
"voucher_type", "voucher_type",
@ -129,12 +131,26 @@
"options": "account_currency" "options": "account_currency"
}, },
{ {
"fieldname": "against", "fieldname": "against_type",
"fieldtype": "Text", "fieldtype": "Link",
"in_filter": 1, "in_filter": 1,
"label": "Against", "label": "Against Type",
"oldfieldname": "against", "options": "DocType"
"oldfieldtype": "Text" },
{
"fieldname": "against",
"fieldtype": "Text",
"in_filter": 1,
"label": "Against",
"oldfieldname": "against",
"oldfieldtype": "Text"
},
{
"fieldname": "against_link",
"fieldtype": "Dynamic Link",
"in_filter": 1,
"label": "Against",
"options": "against_type"
}, },
{ {
"fieldname": "against_voucher_type", "fieldname": "against_voucher_type",
@ -286,7 +302,7 @@
"idx": 1, "idx": 1,
"in_create": 1, "in_create": 1,
"links": [], "links": [],
"modified": "2023-08-16 21:38:44.072267", "modified": "2023-11-08 12:20:23.031733",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "GL Entry", "name": "GL Entry",

View File

@ -153,7 +153,9 @@ class InvoiceDiscounting(AccountsController):
"account": inv.debit_to, "account": inv.debit_to,
"party_type": "Customer", "party_type": "Customer",
"party": d.customer, "party": d.customer,
"against_type": "Account",
"against": self.accounts_receivable_credit, "against": self.accounts_receivable_credit,
"against_link": self.accounts_receivable_credit,
"credit": outstanding_in_company_currency, "credit": outstanding_in_company_currency,
"credit_in_account_currency": outstanding_in_company_currency "credit_in_account_currency": outstanding_in_company_currency
if inv.party_account_currency == company_currency if inv.party_account_currency == company_currency
@ -173,7 +175,9 @@ class InvoiceDiscounting(AccountsController):
"account": self.accounts_receivable_credit, "account": self.accounts_receivable_credit,
"party_type": "Customer", "party_type": "Customer",
"party": d.customer, "party": d.customer,
"against_type": "Account",
"against": inv.debit_to, "against": inv.debit_to,
"against_link": inv.debit_to,
"debit": outstanding_in_company_currency, "debit": outstanding_in_company_currency,
"debit_in_account_currency": outstanding_in_company_currency "debit_in_account_currency": outstanding_in_company_currency
if ar_credit_account_currency == company_currency if ar_credit_account_currency == company_currency

View File

@ -8,7 +8,7 @@ frappe.provide("erpnext.journal_entry");
frappe.ui.form.on("Journal Entry", { frappe.ui.form.on("Journal Entry", {
setup: function(frm) { setup: function(frm) {
frm.add_fetch("bank_account", "account", "account"); frm.add_fetch("bank_account", "account", "account");
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger"]; frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
}, },
refresh: function(frm) { refresh: function(frm) {
@ -220,6 +220,16 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
return erpnext.journal_entry.account_query(me.frm); return erpnext.journal_entry.account_query(me.frm);
}); });
me.frm.set_query("against_account_link", "accounts", function(doc, cdt, cdn) {
return erpnext.journal_entry.against_account_query(me.frm);
});
me.frm.set_query("against_type", "accounts", function(){
return {
query: "erpnext.accounts.doctype.journal_entry.journal_entry.get_against_type",
}
})
me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) { me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) {
const row = locals[cdt][cdn]; const row = locals[cdt][cdn];
@ -591,6 +601,21 @@ $.extend(erpnext.journal_entry, {
return { filters: filters }; return { filters: filters };
}, },
against_account_query: function(frm) {
if (frm.doc.against_type != "Account"){
return { filters: {} };
}
else {
let filters = { company: frm.doc.company, is_group: 0 };
if(!frm.doc.multi_currency) {
$.extend(filters, {
account_currency: ['in', [frappe.get_doc(":Company", frm.doc.company).default_currency, null]]
});
}
return { filters: filters };
}
},
reverse_journal_entry: function() { reverse_journal_entry: function() {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.journal_entry.journal_entry.make_reverse_journal_entry", method: "erpnext.accounts.doctype.journal_entry.journal_entry.make_reverse_journal_entry",

View File

@ -170,6 +170,8 @@ class JournalEntry(AccountsController):
"Repost Payment Ledger Items", "Repost Payment Ledger Items",
"Repost Accounting Ledger", "Repost Accounting Ledger",
"Repost Accounting Ledger Items", "Repost Accounting Ledger Items",
"Unreconcile Payment",
"Unreconcile Payment Entries",
) )
self.make_gl_entries(1) self.make_gl_entries(1)
self.update_advance_paid() self.update_advance_paid()
@ -302,6 +304,7 @@ class JournalEntry(AccountsController):
"account": tax_withholding_details.get("account_head"), "account": tax_withholding_details.get("account_head"),
rev_debit_or_credit: tax_withholding_details.get("tax_amount"), rev_debit_or_credit: tax_withholding_details.get("tax_amount"),
"against_account": parties[0], "against_account": parties[0],
"against_account_link": parties[0],
}, },
) )
@ -629,7 +632,7 @@ class JournalEntry(AccountsController):
) )
# set totals # set totals
if not d.reference_name in self.reference_totals: if d.reference_name not in self.reference_totals:
self.reference_totals[d.reference_name] = 0.0 self.reference_totals[d.reference_name] = 0.0
if self.voucher_type not in ("Deferred Revenue", "Deferred Expense"): if self.voucher_type not in ("Deferred Revenue", "Deferred Expense"):
@ -748,27 +751,90 @@ class JournalEntry(AccountsController):
) )
def set_against_account(self): def set_against_account(self):
accounts_debited, accounts_credited = [], []
if self.voucher_type in ("Deferred Revenue", "Deferred Expense"): if self.voucher_type in ("Deferred Revenue", "Deferred Expense"):
for d in self.get("accounts"): for d in self.get("accounts"):
if d.reference_type == "Sales Invoice": if d.reference_type == "Sales Invoice":
field = "customer" against_type = "Customer"
else: else:
field = "supplier" against_type = "Supplier"
d.against_account = frappe.db.get_value(d.reference_type, d.reference_name, field) against_account = frappe.db.get_value(d.reference_type, d.reference_name, against_type.lower())
d.against_type = against_type
d.against_account_link = against_account
else: else:
for d in self.get("accounts"): self.get_debited_credited_accounts()
if flt(d.debit) > 0: if len(self.accounts_credited) > 1 and len(self.accounts_debited) > 1:
accounts_debited.append(d.party or d.account) self.auto_set_against_accounts()
if flt(d.credit) > 0: return
accounts_credited.append(d.party or d.account) self.get_against_accounts()
for d in self.get("accounts"): def auto_set_against_accounts(self):
if flt(d.debit) > 0: for i in range(0, len(self.accounts), 2):
d.against_account = ", ".join(list(set(accounts_credited))) acc = self.accounts[i]
if flt(d.credit) > 0: against_acc = self.accounts[i + 1]
d.against_account = ", ".join(list(set(accounts_debited))) if acc.debit_in_account_currency > 0:
current_val = acc.debit_in_account_currency * flt(acc.exchange_rate)
against_val = against_acc.credit_in_account_currency * flt(against_acc.exchange_rate)
else:
current_val = acc.credit_in_account_currency * flt(acc.exchange_rate)
against_val = against_acc.debit_in_account_currency * flt(against_acc.exchange_rate)
if current_val == against_val:
acc.against_type = against_acc.party_type or "Account"
against_acc.against_type = acc.party_type or "Account"
acc.against_account_link = against_acc.party or against_acc.account
against_acc.against_account_link = acc.party or acc.account
else:
frappe.msgprint(
_(
"Unable to automatically determine {0} accounts. Set them up in the {1} table if needed."
).format(frappe.bold("against"), frappe.bold("Accounting Entries")),
alert=True,
)
break
def get_against_accounts(self):
self.against_accounts = []
self.split_account = {}
self.get_debited_credited_accounts()
if self.separate_against_account_entries:
no_of_credited_acc, no_of_debited_acc = len(self.accounts_credited), len(self.accounts_debited)
if no_of_credited_acc <= 1 and no_of_debited_acc <= 1:
self.set_against_accounts_for_single_dr_cr()
self.separate_against_account_entries = 0
elif no_of_credited_acc == 1:
self.against_accounts = self.accounts_debited
self.split_account = self.accounts_credited[0]
elif no_of_debited_acc == 1:
self.against_accounts = self.accounts_credited
self.split_account = self.accounts_debited[0]
def get_debited_credited_accounts(self):
self.accounts_debited, self.accounts_credited = [], []
self.separate_against_account_entries = 1
for d in self.get("accounts"):
if flt(d.debit) > 0:
self.accounts_debited.append(d)
elif flt(d.credit) > 0:
self.accounts_credited.append(d)
if d.against_account_link:
self.separate_against_account_entries = 0
break
def set_against_accounts_for_single_dr_cr(self):
against_account = None
for d in self.accounts:
if flt(d.debit) > 0:
against_account = self.accounts_credited[0]
elif flt(d.credit) > 0:
against_account = self.accounts_debited[0]
if against_account:
d.against_type = against_account.party_type or "Account"
d.against_account = against_account.party or against_account.account
d.against_account_link = against_account.party or against_account.account
def validate_debit_credit_amount(self): def validate_debit_credit_amount(self):
if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency): if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency):
@ -965,40 +1031,82 @@ class JournalEntry(AccountsController):
def build_gl_map(self): def build_gl_map(self):
gl_map = [] gl_map = []
self.get_against_accounts()
for d in self.get("accounts"): for d in self.get("accounts"):
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"): if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
r = [d.user_remark, self.remark] r = [d.user_remark, self.remark]
r = [x for x in r if x] r = [x for x in r if x]
remarks = "\n".join(r) remarks = "\n".join(r)
gl_map.append( gl_dict = self.get_gl_dict(
self.get_gl_dict( {
{ "account": d.account,
"account": d.account, "party_type": d.party_type,
"party_type": d.party_type, "due_date": self.due_date,
"due_date": self.due_date, "party": d.party,
"party": d.party, "debit": flt(d.debit, d.precision("debit")),
"against": d.against_account, "credit": flt(d.credit, d.precision("credit")),
"debit": flt(d.debit, d.precision("debit")), "account_currency": d.account_currency,
"credit": flt(d.credit, d.precision("credit")), "debit_in_account_currency": flt(
"account_currency": d.account_currency, d.debit_in_account_currency, d.precision("debit_in_account_currency")
"debit_in_account_currency": flt( ),
d.debit_in_account_currency, d.precision("debit_in_account_currency") "credit_in_account_currency": flt(
), d.credit_in_account_currency, d.precision("credit_in_account_currency")
"credit_in_account_currency": flt( ),
d.credit_in_account_currency, d.precision("credit_in_account_currency") "against_voucher_type": d.reference_type,
), "against_voucher": d.reference_name,
"against_voucher_type": d.reference_type, "remarks": remarks,
"against_voucher": d.reference_name, "voucher_detail_no": d.reference_detail_no,
"remarks": remarks, "cost_center": d.cost_center,
"voucher_detail_no": d.reference_detail_no, "project": d.project,
"cost_center": d.cost_center, "finance_book": self.finance_book,
"project": d.project, },
"finance_book": self.finance_book, item=d,
},
item=d,
)
) )
if not self.separate_against_account_entries:
gl_dict.update(
{
"against_type": d.against_type,
"against_link": d.against_account_link,
}
)
gl_map.append(gl_dict)
elif d in self.against_accounts:
gl_dict.update(
{
"against_type": self.split_account.get("party_type") or "Account",
"against": self.split_account.get("party") or self.split_account.get("account"),
"against_link": self.split_account.get("party") or self.split_account.get("account"),
}
)
gl_map.append(gl_dict)
else:
for against_account in self.against_accounts:
against_account = against_account.as_dict()
debit = against_account.credit or against_account.credit_in_account_currency
credit = against_account.debit or against_account.debit_in_account_currency
gl_dict = gl_dict.copy()
gl_dict.update(
{
"against_type": against_account.party_type or "Account",
"against": against_account.party or against_account.account,
"against_link": against_account.party or against_account.account,
"debit": flt(debit, d.precision("debit")),
"credit": flt(credit, d.precision("credit")),
"account_currency": d.account_currency,
"debit_in_account_currency": flt(
debit / d.exchange_rate, d.precision("debit_in_account_currency")
),
"credit_in_account_currency": flt(
credit / d.exchange_rate, d.precision("credit_in_account_currency")
),
}
)
gl_map.append(gl_dict)
return gl_map return gl_map
def make_gl_entries(self, cancel=0, adv_adj=0): def make_gl_entries(self, cancel=0, adv_adj=0):
@ -1623,3 +1731,10 @@ def make_reverse_journal_entry(source_name, target_doc=None):
) )
return doclist return doclist
@frappe.whitelist()
def get_against_type(doctype, txt, searchfield, start, page_len, filters):
against_types = frappe.db.get_list("Party Type", pluck="name") + ["Account"]
doctype = frappe.qb.DocType("DocType")
return frappe.qb.from_(doctype).select(doctype.name).where(doctype.name.isin(against_types)).run()

View File

@ -37,7 +37,9 @@
"col_break3", "col_break3",
"is_advance", "is_advance",
"user_remark", "user_remark",
"against_account" "against_type",
"against_account",
"against_account_link"
], ],
"fields": [ "fields": [
{ {
@ -250,14 +252,21 @@
"print_hide": 1 "print_hide": 1
}, },
{ {
"fieldname": "against_account", "fieldname": "against_account",
"fieldtype": "Text", "fieldtype": "Text",
"hidden": 1, "hidden": 1,
"label": "Against Account",
"no_copy": 1,
"oldfieldname": "against_account",
"oldfieldtype": "Text",
"print_hide": 1
},
{
"fieldname": "against_account_link",
"fieldtype": "Dynamic Link",
"label": "Against Account", "label": "Against Account",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "against_account", "options": "against_type"
"oldfieldtype": "Text",
"print_hide": 1
}, },
{ {
"collapsible": 1, "collapsible": 1,
@ -280,14 +289,19 @@
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"label": "Reference Detail No", "label": "Reference Detail No",
"no_copy": 1, "no_copy": 1
"search_index": 1 },
{
"fieldname": "against_type",
"fieldtype": "Link",
"label": "Against Type",
"options": "DocType"
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2023-11-23 11:44:25.841187", "modified": "2023-12-02 23:21:22.205409",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Journal Entry Account", "name": "Journal Entry Account",

View File

@ -140,7 +140,7 @@ class TestLoyaltyProgram(unittest.TestCase):
"Loyalty Point Entry", "Loyalty Point Entry",
{"invoice_type": "Sales Invoice", "invoice": si.name, "customer": si.customer}, {"invoice_type": "Sales Invoice", "invoice": si.name, "customer": si.customer},
) )
self.assertEqual(True, not (lpe is None)) self.assertEqual(True, lpe is not None)
# cancelling sales invoice # cancelling sales invoice
si.cancel() si.cancel()

View File

@ -369,12 +369,12 @@ class PaymentEntry(AccountsController):
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 or self.paid_from_account_balance): if self.paid_from and not self.paid_from_account_currency and not self.paid_from_account_balance:
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 self.paid_from_account_balance = acc.account_balance
if self.paid_to and not (self.paid_to_account_currency or self.paid_to_account_balance): if self.paid_to and not self.paid_to_account_currency and not self.paid_to_account_balance:
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.paid_to_account_balance = acc.account_balance
@ -390,8 +390,9 @@ class PaymentEntry(AccountsController):
) -> None: ) -> None:
for d in self.get("references"): for d in self.get("references"):
if d.allocated_amount: if d.allocated_amount:
if update_ref_details_only_for and ( if (
not (d.reference_doctype, d.reference_name) in update_ref_details_only_for update_ref_details_only_for
and (d.reference_doctype, d.reference_name) not in update_ref_details_only_for
): ):
continue continue
@ -702,7 +703,7 @@ class PaymentEntry(AccountsController):
self.db_set("status", self.status, update_modified=True) self.db_set("status", self.status, update_modified=True)
def set_tax_withholding(self): def set_tax_withholding(self):
if not self.party_type == "Supplier": if self.party_type != "Supplier":
return return
if not self.apply_tax_withholding_amount: if not self.apply_tax_withholding_amount:
@ -793,7 +794,7 @@ class PaymentEntry(AccountsController):
self.base_received_amount = self.base_paid_amount self.base_received_amount = self.base_paid_amount
if ( if (
self.paid_from_account_currency == self.paid_to_account_currency self.paid_from_account_currency == self.paid_to_account_currency
and not self.payment_type == "Internal Transfer" and self.payment_type != "Internal Transfer"
): ):
self.received_amount = self.paid_amount self.received_amount = self.paid_amount
@ -1060,7 +1061,9 @@ class PaymentEntry(AccountsController):
"account": self.party_account, "account": self.party_account,
"party_type": self.party_type, "party_type": self.party_type,
"party": self.party, "party": self.party,
"against_type": "Account",
"against": against_account, "against": against_account,
"against_link": against_account,
"account_currency": self.party_account_currency, "account_currency": self.party_account_currency,
"cost_center": self.cost_center, "cost_center": self.cost_center,
}, },
@ -1225,7 +1228,9 @@ class PaymentEntry(AccountsController):
{ {
"account": self.paid_from, "account": self.paid_from,
"account_currency": self.paid_from_account_currency, "account_currency": self.paid_from_account_currency,
"against_type": self.party_type if self.payment_type == "Pay" else "Account",
"against": self.party if self.payment_type == "Pay" else self.paid_to, "against": self.party if self.payment_type == "Pay" else self.paid_to,
"against_link": self.party if self.payment_type == "Pay" else self.paid_to,
"credit_in_account_currency": self.paid_amount, "credit_in_account_currency": self.paid_amount,
"credit": self.base_paid_amount, "credit": self.base_paid_amount,
"cost_center": self.cost_center, "cost_center": self.cost_center,
@ -1240,7 +1245,9 @@ class PaymentEntry(AccountsController):
{ {
"account": self.paid_to, "account": self.paid_to,
"account_currency": self.paid_to_account_currency, "account_currency": self.paid_to_account_currency,
"against_type": self.party_type if self.payment_type == "Receive" else "Account",
"against": self.party if self.payment_type == "Receive" else self.paid_from, "against": self.party if self.payment_type == "Receive" else self.paid_from,
"against_link": self.party if self.payment_type == "Receive" else self.paid_from,
"debit_in_account_currency": self.received_amount, "debit_in_account_currency": self.received_amount,
"debit": self.base_received_amount, "debit": self.base_received_amount,
"cost_center": self.cost_center, "cost_center": self.cost_center,
@ -1264,6 +1271,7 @@ class PaymentEntry(AccountsController):
rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit" rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
against = self.party or self.paid_to against = self.party or self.paid_to
against_type = self.party_type or "Account"
payment_account = self.get_party_account_for_taxes() payment_account = self.get_party_account_for_taxes()
tax_amount = d.tax_amount tax_amount = d.tax_amount
base_tax_amount = d.base_tax_amount base_tax_amount = d.base_tax_amount
@ -1272,7 +1280,9 @@ class PaymentEntry(AccountsController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": d.account_head, "account": d.account_head,
"against_type": against_type,
"against": against, "against": against,
"against_link": against,
dr_or_cr: tax_amount, dr_or_cr: tax_amount,
dr_or_cr + "_in_account_currency": base_tax_amount dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency == self.company_currency if account_currency == self.company_currency
@ -1297,7 +1307,9 @@ class PaymentEntry(AccountsController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": payment_account, "account": payment_account,
"against_type": against_type,
"against": against, "against": against,
"against_link": against,
rev_dr_or_cr: tax_amount, rev_dr_or_cr: tax_amount,
rev_dr_or_cr + "_in_account_currency": base_tax_amount rev_dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency == self.company_currency if account_currency == self.company_currency
@ -1322,7 +1334,9 @@ class PaymentEntry(AccountsController):
{ {
"account": d.account, "account": d.account,
"account_currency": account_currency, "account_currency": account_currency,
"against_type": self.party_type or "Account",
"against": self.party or self.paid_from, "against": self.party or self.paid_from,
"against_link": self.party or self.paid_from,
"debit_in_account_currency": d.amount, "debit_in_account_currency": d.amount,
"debit": d.amount, "debit": d.amount,
"cost_center": d.cost_center, "cost_center": d.cost_center,
@ -1757,7 +1771,7 @@ def get_split_invoice_rows(invoice: dict, payment_term_template: str, exc_rates:
"Payment Schedule", filters={"parent": invoice.voucher_no}, fields=["*"], order_by="due_date" "Payment Schedule", filters={"parent": invoice.voucher_no}, fields=["*"], order_by="due_date"
) )
for payment_term in payment_schedule: for payment_term in payment_schedule:
if not payment_term.outstanding > 0.1: if payment_term.outstanding <= 0.1:
continue continue
doc_details = exc_rates.get(payment_term.parent, None) doc_details = exc_rates.get(payment_term.parent, None)

View File

@ -594,6 +594,27 @@ class PaymentReconciliation(Document):
invoice_exchange_map.update(purchase_invoice_map) invoice_exchange_map.update(purchase_invoice_map)
journals = [
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Journal Entry"
]
journals.extend(
[d.get("reference_name") for d in payments if d.get("reference_type") == "Journal Entry"]
)
if journals:
journals = list(set(journals))
journals_map = frappe._dict(
frappe.db.get_all(
"Journal Entry Account",
filters={"parent": ("in", journals), "account": ("in", [self.receivable_payable_account])},
fields=[
"parent as `name`",
"exchange_rate",
],
as_list=1,
)
)
invoice_exchange_map.update(journals_map)
return invoice_exchange_map return invoice_exchange_map
def validate_allocation(self): def validate_allocation(self):

View File

@ -34,4 +34,6 @@ class PaymentReconciliationAllocation(Document):
unreconciled_amount: DF.Currency unreconciled_amount: DF.Currency
# end: auto-generated types # end: auto-generated types
pass @staticmethod
def get_list(args):
pass

View File

@ -26,4 +26,6 @@ class PaymentReconciliationInvoice(Document):
parenttype: DF.Data parenttype: DF.Data
# end: auto-generated types # end: auto-generated types
pass @staticmethod
def get_list(args):
pass

View File

@ -30,4 +30,6 @@ class PaymentReconciliationPayment(Document):
remark: DF.SmallText | None remark: DF.SmallText | None
# end: auto-generated types # end: auto-generated types
pass @staticmethod
def get_list(args):
pass

View File

@ -286,7 +286,7 @@ class PricingRule(Document):
def validate_price_list_with_currency(self): def validate_price_list_with_currency(self):
if self.currency and self.for_price_list: if self.currency and self.for_price_list:
price_list_currency = frappe.db.get_value("Price List", self.for_price_list, "currency", True) price_list_currency = frappe.db.get_value("Price List", self.for_price_list, "currency", True)
if not self.currency == price_list_currency: if self.currency != price_list_currency:
throw(_("Currency should be same as Price List Currency: {0}").format(price_list_currency)) throw(_("Currency should be same as Price List Currency: {0}").format(price_list_currency))
def validate_dates(self): def validate_dates(self):

View File

@ -581,6 +581,8 @@ def apply_pricing_rule_on_transaction(doc):
if d.price_or_product_discount == "Price": if d.price_or_product_discount == "Price":
if d.apply_discount_on: if d.apply_discount_on:
doc.set("apply_discount_on", d.apply_discount_on) doc.set("apply_discount_on", d.apply_discount_on)
# Variable to track whether the condition has been met
condition_met = False
for field in ["additional_discount_percentage", "discount_amount"]: for field in ["additional_discount_percentage", "discount_amount"]:
pr_field = "discount_percentage" if field == "additional_discount_percentage" else field pr_field = "discount_percentage" if field == "additional_discount_percentage" else field
@ -603,6 +605,11 @@ def apply_pricing_rule_on_transaction(doc):
if coupon_code_pricing_rule == d.name: if coupon_code_pricing_rule == d.name:
# if selected coupon code is linked with pricing rule # if selected coupon code is linked with pricing rule
doc.set(field, d.get(pr_field)) doc.set(field, d.get(pr_field))
# Set the condition_met variable to True and break out of the loop
condition_met = True
break
else: else:
# reset discount if not linked # reset discount if not linked
doc.set(field, 0) doc.set(field, 0)
@ -611,6 +618,10 @@ def apply_pricing_rule_on_transaction(doc):
doc.set(field, 0) doc.set(field, 0)
doc.calculate_taxes_and_totals() doc.calculate_taxes_and_totals()
# Break out of the main loop if the condition is met
if condition_met:
break
elif d.price_or_product_discount == "Product": elif d.price_or_product_discount == "Product":
item_details = frappe._dict({"parenttype": doc.doctype, "free_item_data": []}) item_details = frappe._dict({"parenttype": doc.doctype, "free_item_data": []})
get_product_discount_rule(d, item_details, doc=doc) get_product_discount_rule(d, item_details, doc=doc)

View File

@ -475,7 +475,7 @@ def reconcile(doc: None | str = None) -> None:
frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed") frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed")
else: else:
if not (frappe.db.get_value("Process Payment Reconciliation", doc, "status") == "Paused"): if frappe.db.get_value("Process Payment Reconciliation", doc, "status") != "Paused":
# trigger next batch in job # trigger next batch in job
# generate reconcile job name # generate reconcile job name
allocation = get_next_allocation(log) allocation = get_next_allocation(log)

View File

@ -15,6 +15,7 @@
"group_by", "group_by",
"cost_center", "cost_center",
"territory", "territory",
"ignore_exchange_rate_revaluation_journals",
"column_break_14", "column_break_14",
"to_date", "to_date",
"finance_book", "finance_book",
@ -376,10 +377,16 @@
"fieldname": "pdf_name", "fieldname": "pdf_name",
"fieldtype": "Data", "fieldtype": "Data",
"label": "PDF Name" "label": "PDF Name"
},
{
"default": "0",
"fieldname": "ignore_exchange_rate_revaluation_journals",
"fieldtype": "Check",
"label": "Ignore Exchange Rate Revaluation Journals"
} }
], ],
"links": [], "links": [],
"modified": "2023-08-28 12:59:53.071334", "modified": "2023-12-18 12:20:08.965120",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Process Statement Of Accounts", "name": "Process Statement Of Accounts",

View File

@ -56,6 +56,7 @@ class ProcessStatementOfAccounts(Document):
frequency: DF.Literal["Weekly", "Monthly", "Quarterly"] frequency: DF.Literal["Weekly", "Monthly", "Quarterly"]
from_date: DF.Date | None from_date: DF.Date | None
group_by: DF.Literal["", "Group by Voucher", "Group by Voucher (Consolidated)"] group_by: DF.Literal["", "Group by Voucher", "Group by Voucher (Consolidated)"]
ignore_exchange_rate_revaluation_journals: DF.Check
include_ageing: DF.Check include_ageing: DF.Check
include_break: DF.Check include_break: DF.Check
letter_head: DF.Link | None letter_head: DF.Link | None
@ -119,6 +120,18 @@ def get_statement_dict(doc, get_statement_dict=False):
statement_dict = {} statement_dict = {}
ageing = "" ageing = ""
err_journals = None
if doc.report == "General Ledger" and doc.ignore_exchange_rate_revaluation_journals:
err_journals = frappe.db.get_all(
"Journal Entry",
filters={
"company": doc.company,
"docstatus": 1,
"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
},
as_list=True,
)
for entry in doc.customers: for entry in doc.customers:
if doc.include_ageing: if doc.include_ageing:
ageing = set_ageing(doc, entry) ageing = set_ageing(doc, entry)
@ -131,6 +144,8 @@ def get_statement_dict(doc, get_statement_dict=False):
) )
filters = get_common_filters(doc) filters = get_common_filters(doc)
if err_journals:
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
if doc.report == "General Ledger": if doc.report == "General Ledger":
filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency)) filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))

View File

@ -35,7 +35,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
super.onload(); super.onload();
// Ignore linked advances // Ignore linked advances
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger"]; this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
if(!this.frm.doc.__islocal) { if(!this.frm.doc.__islocal) {
// show credit_to in print format // show credit_to in print format

View File

@ -371,7 +371,7 @@ class PurchaseInvoice(BuyingController):
check_list = [] check_list = []
for d in self.get("items"): for d in self.get("items"):
if d.purchase_order and not d.purchase_order in check_list and not d.purchase_receipt: if d.purchase_order and d.purchase_order not in check_list and not d.purchase_receipt:
check_list.append(d.purchase_order) check_list.append(d.purchase_order)
check_on_hold_or_closed_status("Purchase Order", d.purchase_order) check_on_hold_or_closed_status("Purchase Order", d.purchase_order)
@ -815,7 +815,9 @@ class PurchaseInvoice(BuyingController):
"party_type": "Supplier", "party_type": "Supplier",
"party": self.supplier, "party": self.supplier,
"due_date": self.due_date, "due_date": self.due_date,
"against_type": "Account",
"against": self.against_expense_account, "against": self.against_expense_account,
"against_link": self.against_expense_account,
"credit": base_grand_total, "credit": base_grand_total,
"credit_in_account_currency": base_grand_total "credit_in_account_currency": base_grand_total
if self.party_account_currency == self.company_currency if self.party_account_currency == self.company_currency
@ -888,7 +890,9 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": warehouse_account[item.warehouse]["account"], "account": warehouse_account[item.warehouse]["account"],
"against_type": "Account",
"against": warehouse_account[item.from_warehouse]["account"], "against": warehouse_account[item.from_warehouse]["account"],
"against_link": warehouse_account[item.from_warehouse]["account"],
"cost_center": item.cost_center, "cost_center": item.cost_center,
"project": item.project or self.project, "project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
@ -908,7 +912,9 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": warehouse_account[item.from_warehouse]["account"], "account": warehouse_account[item.from_warehouse]["account"],
"against_type": "Account",
"against": warehouse_account[item.warehouse]["account"], "against": warehouse_account[item.warehouse]["account"],
"against_link": warehouse_account[item.warehouse]["account"],
"cost_center": item.cost_center, "cost_center": item.cost_center,
"project": item.project or self.project, "project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
@ -925,7 +931,9 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": item.expense_account, "account": item.expense_account,
"against_type": "Supplier",
"against": self.supplier, "against": self.supplier,
"against_link": self.supplier,
"debit": flt(item.base_net_amount, item.precision("base_net_amount")), "debit": flt(item.base_net_amount, item.precision("base_net_amount")),
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center, "cost_center": item.cost_center,
@ -942,7 +950,9 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": item.expense_account, "account": item.expense_account,
"against_type": "Supplier",
"against": self.supplier, "against": self.supplier,
"against_link": self.supplier,
"debit": warehouse_debit_amount, "debit": warehouse_debit_amount,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center, "cost_center": item.cost_center,
@ -961,7 +971,9 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": account, "account": account,
"against_type": "Account",
"against": item.expense_account, "against": item.expense_account,
"against_link": item.expense_account,
"cost_center": item.cost_center, "cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(amount["base_amount"]), "credit": flt(amount["base_amount"]),
@ -981,7 +993,9 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": supplier_warehouse_account, "account": supplier_warehouse_account,
"against_type": "Account",
"against": item.expense_account, "against": item.expense_account,
"against_link": item.expense_account,
"cost_center": item.cost_center, "cost_center": item.cost_center,
"project": item.project or self.project, "project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
@ -1036,7 +1050,9 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": expense_account, "account": expense_account,
"against_type": "Supplier",
"against": self.supplier, "against": self.supplier,
"against_link": self.supplier,
"debit": amount, "debit": amount,
"cost_center": item.cost_center, "cost_center": item.cost_center,
"project": item.project or self.project, "project": item.project or self.project,
@ -1062,7 +1078,9 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": expense_account, "account": expense_account,
"against_type": "Supplier",
"against": self.supplier, "against": self.supplier,
"against_link": self.supplier,
"debit": discrepancy_caused_by_exchange_rate_difference, "debit": discrepancy_caused_by_exchange_rate_difference,
"cost_center": item.cost_center, "cost_center": item.cost_center,
"project": item.project or self.project, "project": item.project or self.project,
@ -1075,7 +1093,9 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": self.get_company_default("exchange_gain_loss_account"), "account": self.get_company_default("exchange_gain_loss_account"),
"against_type": "Supplier",
"against": self.supplier, "against": self.supplier,
"against_link": self.supplier,
"credit": discrepancy_caused_by_exchange_rate_difference, "credit": discrepancy_caused_by_exchange_rate_difference,
"cost_center": item.cost_center, "cost_center": item.cost_center,
"project": item.project or self.project, "project": item.project or self.project,
@ -1120,7 +1140,9 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": stock_rbnb, "account": stock_rbnb,
"against_type": "Supplier",
"against": self.supplier, "against": self.supplier,
"against_link": self.supplier,
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")), "debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
"remarks": self.remarks or _("Accounting Entry for Stock"), "remarks": self.remarks or _("Accounting Entry for Stock"),
"cost_center": self.cost_center, "cost_center": self.cost_center,
@ -1168,7 +1190,9 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": cost_of_goods_sold_account, "account": cost_of_goods_sold_account,
"against_type": "Account",
"against": item.expense_account, "against": item.expense_account,
"against_link": item.expense_account,
"debit": stock_adjustment_amt, "debit": stock_adjustment_amt,
"remarks": self.get("remarks") or _("Stock Adjustment"), "remarks": self.get("remarks") or _("Stock Adjustment"),
"cost_center": item.cost_center, "cost_center": item.cost_center,
@ -1198,7 +1222,9 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": tax.account_head, "account": tax.account_head,
"against_type": "Supplier",
"against": self.supplier, "against": self.supplier,
"against_link": self.supplier,
dr_or_cr: base_amount, dr_or_cr: base_amount,
dr_or_cr + "_in_account_currency": base_amount dr_or_cr + "_in_account_currency": base_amount
if account_currency == self.company_currency if account_currency == self.company_currency
@ -1246,8 +1272,10 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": tax.account_head, "account": tax.account_head,
"against_type": "Supplier",
"cost_center": tax.cost_center, "cost_center": tax.cost_center,
"against": self.supplier, "against": self.supplier,
"against_link": self.supplier,
"credit": applicable_amount, "credit": applicable_amount,
"remarks": self.remarks or _("Accounting Entry for Stock"), "remarks": self.remarks or _("Accounting Entry for Stock"),
}, },
@ -1265,7 +1293,9 @@ class PurchaseInvoice(BuyingController):
{ {
"account": tax.account_head, "account": tax.account_head,
"cost_center": tax.cost_center, "cost_center": tax.cost_center,
"against_type": "Supplier",
"against": self.supplier, "against": self.supplier,
"against_link": self.supplier,
"credit": valuation_tax[tax.name], "credit": valuation_tax[tax.name],
"remarks": self.remarks or _("Accounting Entry for Stock"), "remarks": self.remarks or _("Accounting Entry for Stock"),
}, },
@ -1280,7 +1310,9 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": self.unrealized_profit_loss_account, "account": self.unrealized_profit_loss_account,
"against_type": "Supplier",
"against": self.supplier, "against": self.supplier,
"against_link": self.supplier,
"credit": flt(self.total_taxes_and_charges), "credit": flt(self.total_taxes_and_charges),
"credit_in_account_currency": flt(self.base_total_taxes_and_charges), "credit_in_account_currency": flt(self.base_total_taxes_and_charges),
"cost_center": self.cost_center, "cost_center": self.cost_center,
@ -1301,7 +1333,9 @@ class PurchaseInvoice(BuyingController):
"account": self.credit_to, "account": self.credit_to,
"party_type": "Supplier", "party_type": "Supplier",
"party": self.supplier, "party": self.supplier,
"against_type": "Account",
"against": self.cash_bank_account, "against": self.cash_bank_account,
"against_link": self.cash_bank_account,
"debit": self.base_paid_amount, "debit": self.base_paid_amount,
"debit_in_account_currency": self.base_paid_amount "debit_in_account_currency": self.base_paid_amount
if self.party_account_currency == self.company_currency if self.party_account_currency == self.company_currency
@ -1322,7 +1356,9 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": self.cash_bank_account, "account": self.cash_bank_account,
"against_type": "Supplier",
"against": self.supplier, "against": self.supplier,
"against_link": self.supplier,
"credit": self.base_paid_amount, "credit": self.base_paid_amount,
"credit_in_account_currency": self.base_paid_amount "credit_in_account_currency": self.base_paid_amount
if bank_account_currency == self.company_currency if bank_account_currency == self.company_currency
@ -1346,7 +1382,9 @@ class PurchaseInvoice(BuyingController):
"account": self.credit_to, "account": self.credit_to,
"party_type": "Supplier", "party_type": "Supplier",
"party": self.supplier, "party": self.supplier,
"against_type": "Account",
"against": self.write_off_account, "against": self.write_off_account,
"against_link": self.write_off_account,
"debit": self.base_write_off_amount, "debit": self.base_write_off_amount,
"debit_in_account_currency": self.base_write_off_amount "debit_in_account_currency": self.base_write_off_amount
if self.party_account_currency == self.company_currency if self.party_account_currency == self.company_currency
@ -1366,7 +1404,9 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": self.write_off_account, "account": self.write_off_account,
"against_type": "Supplier",
"against": self.supplier, "against": self.supplier,
"against_link": self.supplier,
"credit": flt(self.base_write_off_amount), "credit": flt(self.base_write_off_amount),
"credit_in_account_currency": self.base_write_off_amount "credit_in_account_currency": self.base_write_off_amount
if write_off_account_currency == self.company_currency if write_off_account_currency == self.company_currency
@ -1393,7 +1433,9 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": round_off_account, "account": round_off_account,
"against_type": "Supplier",
"against": self.supplier, "against": self.supplier,
"against_link": self.supplier,
"debit_in_account_currency": self.rounding_adjustment, "debit_in_account_currency": self.rounding_adjustment,
"debit": self.base_rounding_adjustment, "debit": self.base_rounding_adjustment,
"cost_center": round_off_cost_center "cost_center": round_off_cost_center
@ -1449,6 +1491,8 @@ class PurchaseInvoice(BuyingController):
"Repost Payment Ledger Items", "Repost Payment Ledger Items",
"Repost Accounting Ledger", "Repost Accounting Ledger",
"Repost Accounting Ledger Items", "Repost Accounting Ledger Items",
"Unreconcile Payment",
"Unreconcile Payment Entries",
"Payment Ledger Entry", "Payment Ledger Entry",
"Tax Withheld Vouchers", "Tax Withheld Vouchers",
"Serial and Batch Bundle", "Serial and Batch Bundle",

View File

@ -126,7 +126,7 @@ class RepostAccountingLedger(Document):
return rendered_page return rendered_page
def on_submit(self): def on_submit(self):
if len(self.vouchers) > 1: if len(self.vouchers) > 5:
job_name = "repost_accounting_ledger_" + self.name job_name = "repost_accounting_ledger_" + self.name
frappe.enqueue( frappe.enqueue(
method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost", method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
@ -170,8 +170,6 @@ def start_repost(account_repost_doc=str) -> None:
doc.make_gl_entries(1) doc.make_gl_entries(1)
doc.make_gl_entries() doc.make_gl_entries()
frappe.db.commit()
def get_allowed_types_from_settings(): def get_allowed_types_from_settings():
return [ return [

View File

@ -20,18 +20,11 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
self.create_company() self.create_company()
self.create_customer() self.create_customer()
self.create_item() self.create_item()
self.update_repost_settings() update_repost_settings()
def teadDown(self): def tearDown(self):
frappe.db.rollback() frappe.db.rollback()
def update_repost_settings(self):
allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
for x in allowed_types:
repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
repost_settings.save()
def test_01_basic_functions(self): def test_01_basic_functions(self):
si = create_sales_invoice( si = create_sales_invoice(
item=self.item, item=self.item,
@ -90,9 +83,6 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
# Submit repost document # Submit repost document
ral.save().submit() ral.save().submit()
# background jobs don't run on test cases. Manually triggering repost function.
start_repost(ral.name)
res = ( res = (
qb.from_(gl) qb.from_(gl)
.select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit")) .select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit"))
@ -177,26 +167,6 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
pe = get_payment_entry(si.doctype, si.name) pe = get_payment_entry(si.doctype, si.name)
pe.save().submit() pe.save().submit()
# without deletion flag set
ral = frappe.new_doc("Repost Accounting Ledger")
ral.company = self.company
ral.delete_cancelled_entries = False
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
ral.save()
# assert preview data is generated
preview = ral.generate_preview()
self.assertIsNotNone(preview)
ral.save().submit()
# background jobs don't run on test cases. Manually triggering repost function.
start_repost(ral.name)
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
# with deletion flag set # with deletion flag set
ral = frappe.new_doc("Repost Accounting Ledger") ral = frappe.new_doc("Repost Accounting Ledger")
ral.company = self.company ral.company = self.company
@ -205,6 +175,38 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name}) ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
ral.save().submit() ral.save().submit()
start_repost(ral.name)
self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1})) self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1})) self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
def test_05_without_deletion_flag(self):
si = create_sales_invoice(
item=self.item,
company=self.company,
customer=self.customer,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
cost_center=self.cost_center,
rate=100,
)
pe = get_payment_entry(si.doctype, si.name)
pe.save().submit()
# without deletion flag set
ral = frappe.new_doc("Repost Accounting Ledger")
ral.company = self.company
ral.delete_cancelled_entries = False
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
ral.save().submit()
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
def update_repost_settings():
allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
for x in allowed_types:
repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
repost_settings.save()

View File

@ -7,7 +7,7 @@ from frappe import _, msgprint, throw
from frappe.contacts.doctype.address.address import get_address_display from frappe.contacts.doctype.address.address import get_address_display
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from frappe.model.utils import get_fetch_values from frappe.model.utils import get_fetch_values
from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate from frappe.utils import add_days, cint, flt, formatdate, get_link_to_form, getdate, nowdate
import erpnext import erpnext
from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.accounts.deferred_revenue import validate_service_stop_date
@ -458,7 +458,7 @@ class SalesInvoice(SellingController):
self.update_billing_status_for_zero_amount_refdoc("Sales Order") self.update_billing_status_for_zero_amount_refdoc("Sales Order")
self.check_credit_limit() self.check_credit_limit()
if not cint(self.is_pos) == 1 and not self.is_return: if cint(self.is_pos) != 1 and not self.is_return:
self.update_against_document_in_jv() self.update_against_document_in_jv()
self.update_time_sheet(self.name) self.update_time_sheet(self.name)
@ -1225,7 +1225,9 @@ class SalesInvoice(SellingController):
"party_type": "Customer", "party_type": "Customer",
"party": self.customer, "party": self.customer,
"due_date": self.due_date, "due_date": self.due_date,
"against_type": "Account",
"against": self.against_income_account, "against": self.against_income_account,
"against_link": self.against_income_account,
"debit": base_grand_total, "debit": base_grand_total,
"debit_in_account_currency": base_grand_total "debit_in_account_currency": base_grand_total
if self.party_account_currency == self.company_currency if self.party_account_currency == self.company_currency
@ -1254,7 +1256,9 @@ class SalesInvoice(SellingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": tax.account_head, "account": tax.account_head,
"against_type": "Customer",
"against": self.customer, "against": self.customer,
"against_link": self.customer,
"credit": flt(base_amount, tax.precision("tax_amount_after_discount_amount")), "credit": flt(base_amount, tax.precision("tax_amount_after_discount_amount")),
"credit_in_account_currency": ( "credit_in_account_currency": (
flt(base_amount, tax.precision("base_tax_amount_after_discount_amount")) flt(base_amount, tax.precision("base_tax_amount_after_discount_amount"))
@ -1275,7 +1279,9 @@ class SalesInvoice(SellingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": self.unrealized_profit_loss_account, "account": self.unrealized_profit_loss_account,
"against_type": "Customer",
"against": self.customer, "against": self.customer,
"against_link": self.customer,
"debit": flt(self.total_taxes_and_charges), "debit": flt(self.total_taxes_and_charges),
"debit_in_account_currency": flt(self.base_total_taxes_and_charges), "debit_in_account_currency": flt(self.base_total_taxes_and_charges),
"cost_center": self.cost_center, "cost_center": self.cost_center,
@ -1343,7 +1349,9 @@ class SalesInvoice(SellingController):
add_asset_activity(asset.name, _("Asset sold")) add_asset_activity(asset.name, _("Asset sold"))
for gle in fixed_asset_gl_entries: for gle in fixed_asset_gl_entries:
gle["against_type"] = "Customer"
gle["against"] = self.customer gle["against"] = self.customer
gle["against_link"] = self.customer
gl_entries.append(self.get_gl_dict(gle, item=item)) gl_entries.append(self.get_gl_dict(gle, item=item))
self.set_asset_status(asset) self.set_asset_status(asset)
@ -1364,7 +1372,9 @@ class SalesInvoice(SellingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": income_account, "account": income_account,
"against_type": "Customer",
"against": self.customer, "against": self.customer,
"against_link": self.customer,
"credit": flt(base_amount, item.precision("base_net_amount")), "credit": flt(base_amount, item.precision("base_net_amount")),
"credit_in_account_currency": ( "credit_in_account_currency": (
flt(base_amount, item.precision("base_net_amount")) flt(base_amount, item.precision("base_net_amount"))
@ -1418,9 +1428,9 @@ class SalesInvoice(SellingController):
"account": self.debit_to, "account": self.debit_to,
"party_type": "Customer", "party_type": "Customer",
"party": self.customer, "party": self.customer,
"against": "Expense account - " "against_type": "Account",
+ cstr(self.loyalty_redemption_account) "against": self.loyalty_redemption_account,
+ " for the Loyalty Program", "against_link": self.loyalty_redemption_account,
"credit": self.loyalty_amount, "credit": self.loyalty_amount,
"against_voucher": self.return_against if cint(self.is_return) else self.name, "against_voucher": self.return_against if cint(self.is_return) else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
@ -1434,7 +1444,9 @@ class SalesInvoice(SellingController):
{ {
"account": self.loyalty_redemption_account, "account": self.loyalty_redemption_account,
"cost_center": self.cost_center or self.loyalty_redemption_cost_center, "cost_center": self.cost_center or self.loyalty_redemption_cost_center,
"against_type": "Customer",
"against": self.customer, "against": self.customer,
"against_link": self.customer,
"debit": self.loyalty_amount, "debit": self.loyalty_amount,
"remark": "Loyalty Points redeemed by the customer", "remark": "Loyalty Points redeemed by the customer",
}, },
@ -1461,7 +1473,9 @@ class SalesInvoice(SellingController):
"account": self.debit_to, "account": self.debit_to,
"party_type": "Customer", "party_type": "Customer",
"party": self.customer, "party": self.customer,
"against_type": "Account",
"against": payment_mode.account, "against": payment_mode.account,
"against_link": payment_mode.account,
"credit": payment_mode.base_amount, "credit": payment_mode.base_amount,
"credit_in_account_currency": payment_mode.base_amount "credit_in_account_currency": payment_mode.base_amount
if self.party_account_currency == self.company_currency if self.party_account_currency == self.company_currency
@ -1482,7 +1496,9 @@ class SalesInvoice(SellingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": payment_mode.account, "account": payment_mode.account,
"against_type": "Customer",
"against": self.customer, "against": self.customer,
"against_link": self.customer,
"debit": payment_mode.base_amount, "debit": payment_mode.base_amount,
"debit_in_account_currency": payment_mode.base_amount "debit_in_account_currency": payment_mode.base_amount
if payment_mode_account_currency == self.company_currency if payment_mode_account_currency == self.company_currency
@ -1506,7 +1522,9 @@ class SalesInvoice(SellingController):
"account": self.debit_to, "account": self.debit_to,
"party_type": "Customer", "party_type": "Customer",
"party": self.customer, "party": self.customer,
"against_type": "Account",
"against": self.account_for_change_amount, "against": self.account_for_change_amount,
"against_link": self.account_for_change_amount,
"debit": flt(self.base_change_amount), "debit": flt(self.base_change_amount),
"debit_in_account_currency": flt(self.base_change_amount) "debit_in_account_currency": flt(self.base_change_amount)
if self.party_account_currency == self.company_currency if self.party_account_currency == self.company_currency
@ -1527,7 +1545,9 @@ class SalesInvoice(SellingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": self.account_for_change_amount, "account": self.account_for_change_amount,
"against_type": "Customer",
"against": self.customer, "against": self.customer,
"against_link": self.customer,
"credit": self.base_change_amount, "credit": self.base_change_amount,
"cost_center": self.cost_center, "cost_center": self.cost_center,
}, },
@ -1553,7 +1573,9 @@ class SalesInvoice(SellingController):
"account": self.debit_to, "account": self.debit_to,
"party_type": "Customer", "party_type": "Customer",
"party": self.customer, "party": self.customer,
"against_type": "Account",
"against": self.write_off_account, "against": self.write_off_account,
"against_link": self.write_off_account,
"credit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")), "credit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")),
"credit_in_account_currency": ( "credit_in_account_currency": (
flt(self.base_write_off_amount, self.precision("base_write_off_amount")) flt(self.base_write_off_amount, self.precision("base_write_off_amount"))
@ -1573,7 +1595,9 @@ class SalesInvoice(SellingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": self.write_off_account, "account": self.write_off_account,
"against_type": "Customer",
"against": self.customer, "against": self.customer,
"against_link": self.customer,
"debit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")), "debit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")),
"debit_in_account_currency": ( "debit_in_account_currency": (
flt(self.base_write_off_amount, self.precision("base_write_off_amount")) flt(self.base_write_off_amount, self.precision("base_write_off_amount"))
@ -1601,7 +1625,9 @@ class SalesInvoice(SellingController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": round_off_account, "account": round_off_account,
"against_type": "Customer",
"against": self.customer, "against": self.customer,
"against_link": self.customer,
"credit_in_account_currency": flt( "credit_in_account_currency": flt(
self.rounding_adjustment, self.precision("rounding_adjustment") self.rounding_adjustment, self.precision("rounding_adjustment")
), ),
@ -1960,9 +1986,9 @@ def validate_inter_company_party(doctype, party, company, inter_company_referenc
if inter_company_reference: if inter_company_reference:
doc = frappe.get_doc(ref_doc, inter_company_reference) doc = frappe.get_doc(ref_doc, inter_company_reference)
ref_party = doc.supplier if doctype in ["Sales Invoice", "Sales Order"] else doc.customer ref_party = doc.supplier if doctype in ["Sales Invoice", "Sales Order"] else doc.customer
if not frappe.db.get_value(partytype, {"represents_company": doc.company}, "name") == party: if frappe.db.get_value(partytype, {"represents_company": doc.company}, "name") != party:
frappe.throw(_("Invalid {0} for Inter Company Transaction.").format(_(partytype))) frappe.throw(_("Invalid {0} for Inter Company Transaction.").format(_(partytype)))
if not frappe.get_cached_value(ref_partytype, ref_party, "represents_company") == company: if frappe.get_cached_value(ref_partytype, ref_party, "represents_company") != company:
frappe.throw(_("Invalid Company for Inter Company Transaction.")) frappe.throw(_("Invalid Company for Inter Company Transaction."))
elif frappe.db.get_value(partytype, {"name": party, internal: 1}, "name") == party: elif frappe.db.get_value(partytype, {"name": party, internal: 1}, "name") == party:
@ -1972,7 +1998,7 @@ def validate_inter_company_party(doctype, party, company, inter_company_referenc
filters={"parenttype": partytype, "parent": party}, filters={"parenttype": partytype, "parent": party},
) )
companies = [d.company for d in companies] companies = [d.company for d in companies]
if not company in companies: if company not in companies:
frappe.throw( frappe.throw(
_("{0} not allowed to transact with {1}. Please change the Company.").format( _("{0} not allowed to transact with {1}. Please change the Company.").format(
_(partytype), company _(partytype), company
@ -2356,9 +2382,18 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
def get_received_items(reference_name, doctype, reference_fieldname): def get_received_items(reference_name, doctype, reference_fieldname):
reference_field = "inter_company_invoice_reference"
if doctype == "Purchase Order":
reference_field = "inter_company_order_reference"
filters = {
reference_field: reference_name,
"docstatus": 1,
}
target_doctypes = frappe.get_all( target_doctypes = frappe.get_all(
doctype, doctype,
filters={"inter_company_invoice_reference": reference_name, "docstatus": 1}, filters=filters,
as_list=True, as_list=True,
) )

View File

@ -2793,6 +2793,12 @@ class TestSalesInvoice(FrappeTestCase):
@change_settings("Selling Settings", {"enable_discount_accounting": 1}) @change_settings("Selling Settings", {"enable_discount_accounting": 1})
def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self): def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
from erpnext.accounts.doctype.repost_accounting_ledger.test_repost_accounting_ledger import (
update_repost_settings,
)
update_repost_settings()
additional_discount_account = create_account( additional_discount_account = create_account(
account_name="Discount Account", account_name="Discount Account",
parent_account="Indirect Expenses - _TC", parent_account="Indirect Expenses - _TC",

View File

@ -142,12 +142,12 @@ class ShippingRule(Document):
} }
if self.shipping_rule_type == "Selling": if self.shipping_rule_type == "Selling":
# check if not applied on purchase # check if not applied on purchase
if not doc.meta.get_field("taxes").options == "Sales Taxes and Charges": if doc.meta.get_field("taxes").options != "Sales Taxes and Charges":
frappe.throw(_("Shipping rule only applicable for Selling")) frappe.throw(_("Shipping rule only applicable for Selling"))
shipping_charge["doctype"] = "Sales Taxes and Charges" shipping_charge["doctype"] = "Sales Taxes and Charges"
else: else:
# check if not applied on sales # check if not applied on sales
if not doc.meta.get_field("taxes").options == "Purchase Taxes and Charges": if doc.meta.get_field("taxes").options != "Purchase Taxes and Charges":
frappe.throw(_("Shipping rule only applicable for Buying")) frappe.throw(_("Shipping rule only applicable for Buying"))
shipping_charge["doctype"] = "Purchase Taxes and Charges" shipping_charge["doctype"] = "Purchase Taxes and Charges"

View File

@ -676,7 +676,7 @@ class Subscription(Document):
to_generate_invoice = ( to_generate_invoice = (
True True
if self.status == "Active" if self.status == "Active"
and not self.generate_invoice_at == "Beginning of the current subscription period" and self.generate_invoice_at != "Beginning of the current subscription period"
else False else False
) )
self.status = "Cancelled" self.status = "Cancelled"
@ -694,7 +694,7 @@ class Subscription(Document):
subscription and the `Subscription` will lose all the history of generated invoices subscription and the `Subscription` will lose all the history of generated invoices
it has. it has.
""" """
if not self.status == "Cancelled": if self.status != "Cancelled":
frappe.throw(_("You cannot restart a Subscription that is not cancelled."), InvoiceNotCancelled) frappe.throw(_("You cannot restart a Subscription that is not cancelled."), InvoiceNotCancelled)
self.status = "Active" self.status = "Active"

View File

@ -37,7 +37,7 @@ class UnreconcilePayment(Document):
def validate(self): def validate(self):
self.supported_types = ["Payment Entry", "Journal Entry"] self.supported_types = ["Payment Entry", "Journal Entry"]
if not self.voucher_type in self.supported_types: if self.voucher_type not in self.supported_types:
frappe.throw(_("Only {0} are supported").format(comma_and(self.supported_types))) frappe.throw(_("Only {0} are supported").format(comma_and(self.supported_types)))
@frappe.whitelist() @frappe.whitelist()

View File

@ -280,6 +280,7 @@ def check_if_in_list(gle, gl_map, dimensions=None):
"project", "project",
"finance_book", "finance_book",
"voucher_no", "voucher_no",
"against_link",
] ]
if dimensions: if dimensions:

View File

@ -775,7 +775,7 @@ def validate_party_frozen_disabled(party_type, party_name):
frozen_accounts_modifier = frappe.db.get_single_value( frozen_accounts_modifier = frappe.db.get_single_value(
"Accounts Settings", "frozen_accounts_modifier" "Accounts Settings", "frozen_accounts_modifier"
) )
if not frozen_accounts_modifier in frappe.get_roles(): if frozen_accounts_modifier not in frappe.get_roles():
frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen) frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen)
elif party_type == "Employee": elif party_type == "Employee":

View File

@ -123,7 +123,7 @@ class ReceivablePayableReport(object):
else: else:
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party) key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
if not key in self.voucher_balance: if key not in self.voucher_balance:
self.voucher_balance[key] = frappe._dict( self.voucher_balance[key] = frappe._dict(
voucher_type=ple.voucher_type, voucher_type=ple.voucher_type,
voucher_no=ple.voucher_no, voucher_no=ple.voucher_no,
@ -225,7 +225,7 @@ class ReceivablePayableReport(object):
if not row: if not row:
return return
if self.filters.get("in_party_currency"): if self.filters.get("in_party_currency") or self.filters.get("party_account"):
amount = ple.amount_in_account_currency amount = ple.amount_in_account_currency
else: else:
amount = ple.amount amount = ple.amount
@ -244,8 +244,12 @@ class ReceivablePayableReport(object):
row.invoiced_in_account_currency += amount_in_account_currency row.invoiced_in_account_currency += amount_in_account_currency
else: else:
if self.is_invoice(ple): if self.is_invoice(ple):
row.credit_note -= amount if row.voucher_no == ple.voucher_no == ple.against_voucher_no:
row.credit_note_in_account_currency -= amount_in_account_currency row.paid -= amount
row.paid_in_account_currency -= amount_in_account_currency
else:
row.credit_note -= amount
row.credit_note_in_account_currency -= amount_in_account_currency
else: else:
row.paid -= amount row.paid -= amount
row.paid_in_account_currency -= amount_in_account_currency row.paid_in_account_currency -= amount_in_account_currency
@ -451,7 +455,7 @@ class ReceivablePayableReport(object):
party_details = self.get_party_details(row.party) or {} party_details = self.get_party_details(row.party) or {}
row.update(party_details) row.update(party_details)
if self.filters.get("in_party_currency"): if self.filters.get("in_party_currency") or self.filters.get("party_account"):
row.currency = row.account_currency row.currency = row.account_currency
else: else:
row.currency = self.company_currency row.currency = self.company_currency
@ -938,7 +942,7 @@ class ReceivablePayableReport(object):
return True return True
def get_party_details(self, party): def get_party_details(self, party):
if not party in self.party_details: if party not in self.party_details:
if self.account_type == "Receivable": if self.account_type == "Receivable":
fields = ["customer_name", "territory", "customer_group", "customer_primary_contact"] fields = ["customer_name", "territory", "customer_group", "customer_primary_contact"]

View File

@ -76,6 +76,41 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
return credit_note return credit_note
def test_pos_receivable(self):
filters = {
"company": self.company,
"party_type": "Customer",
"party": [self.customer],
"report_date": add_days(today(), 2),
"based_on_payment_terms": 0,
"range1": 30,
"range2": 60,
"range3": 90,
"range4": 120,
"show_remarks": False,
}
pos_inv = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
pos_inv.posting_date = add_days(today(), 2)
pos_inv.is_pos = 1
pos_inv.append(
"payments",
frappe._dict(
mode_of_payment="Cash",
amount=flt(pos_inv.grand_total / 2),
),
)
pos_inv.disable_rounded_total = 1
pos_inv.save()
pos_inv.submit()
report = execute(filters)
expected_data = [[pos_inv.grand_total, pos_inv.paid_amount, 0]]
row = report[1][-1]
self.assertEqual(expected_data[0], [row.invoiced, row.paid, row.credit_note])
pos_inv.cancel()
def test_accounts_receivable(self): def test_accounts_receivable(self):
filters = { filters = {
"company": self.company, "company": self.company,
@ -544,7 +579,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
filters.update({"party_account": self.debtors_usd}) filters.update({"party_account": self.debtors_usd})
report = execute(filters)[1] report = execute(filters)[1]
self.assertEqual(len(report), 1) self.assertEqual(len(report), 1)
expected_data = [8000.0, 8000.0, self.debtors_usd, si2.currency] expected_data = [100.0, 100.0, self.debtors_usd, si2.currency]
row = report[0] row = report[0]
self.assertEqual( self.assertEqual(
expected_data, [row.invoiced, row.outstanding, row.party_account, row.account_currency] expected_data, [row.invoiced, row.outstanding, row.party_account, row.account_currency]

View File

@ -97,7 +97,7 @@ class Deferred_Item(object):
if base_amount + already_booked_amount > self.base_net_amount: if base_amount + already_booked_amount > self.base_net_amount:
base_amount = self.base_net_amount - already_booked_amount base_amount = self.base_net_amount - already_booked_amount
if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date): if get_first_day(start_date) != start_date or get_last_day(end_date) != end_date:
partial_month = flt(date_diff(end_date, start_date)) / flt( partial_month = flt(date_diff(end_date, start_date)) / flt(
date_diff(get_last_day(end_date), get_first_day(start_date)) date_diff(get_last_day(end_date), get_first_day(start_date))
) )

View File

@ -8,7 +8,17 @@ import re
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate from frappe.utils import (
add_days,
add_months,
cint,
cstr,
flt,
formatdate,
get_first_day,
getdate,
today,
)
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import ( from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions, get_accounting_dimensions,
@ -43,6 +53,8 @@ def get_period_list(
year_start_date = getdate(period_start_date) year_start_date = getdate(period_start_date)
year_end_date = getdate(period_end_date) year_end_date = getdate(period_end_date)
year_end_date = getdate(today()) if year_end_date > getdate(today()) else year_end_date
months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity] months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity]
period_list = [] period_list = []

View File

@ -203,7 +203,7 @@ def get_gl_entries(filters, accounting_dimensions):
voucher_type, voucher_no, {dimension_fields} voucher_type, voucher_no, {dimension_fields}
cost_center, project, {transaction_currency_fields} cost_center, project, {transaction_currency_fields}
against_voucher_type, against_voucher, account_currency, against_voucher_type, against_voucher, account_currency,
against, is_opening, creation {select_fields} against_link, against, is_opening, creation {select_fields}
from `tabGL Entry` from `tabGL Entry`
where company=%(company)s {conditions} where company=%(company)s {conditions}
{order_by_statement} {order_by_statement}
@ -238,6 +238,9 @@ def get_conditions(filters):
if filters.get("voucher_no"): if filters.get("voucher_no"):
conditions.append("voucher_no=%(voucher_no)s") conditions.append("voucher_no=%(voucher_no)s")
if filters.get("voucher_no_not_in"):
conditions.append("voucher_no not in %(voucher_no_not_in)s")
if filters.get("group_by") == "Group by Party" and not filters.get("party_type"): if filters.get("group_by") == "Group by Party" and not filters.get("party_type"):
conditions.append("party_type in ('Customer', 'Supplier')") conditions.append("party_type in ('Customer', 'Supplier')")
@ -289,7 +292,8 @@ def get_conditions(filters):
if accounting_dimensions: if accounting_dimensions:
for dimension in accounting_dimensions: for dimension in accounting_dimensions:
if not dimension.disabled: # Ignore 'Finance Book' set up as dimension in below logic, as it is already handled in above section
if not dimension.disabled and dimension.document_type != "Finance Book":
if filters.get(dimension.fieldname): if filters.get(dimension.fieldname):
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"): if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
filters[dimension.fieldname] = get_dimension_with_children( filters[dimension.fieldname] = get_dimension_with_children(
@ -391,6 +395,7 @@ def initialize_gle_map(gl_entries, filters):
group_by = group_by_field(filters.get("group_by")) group_by = group_by_field(filters.get("group_by"))
for gle in gl_entries: for gle in gl_entries:
gle.against = gle.get("against_link") or gle.get("against")
gle_map.setdefault(gle.get(group_by), _dict(totals=get_totals_dict(), entries=[])) gle_map.setdefault(gle.get(group_by), _dict(totals=get_totals_dict(), entries=[]))
return gle_map return gle_map

View File

@ -170,7 +170,7 @@ def set_total(node, value, complete_list, totals):
totals[node["account"]] += value totals[node["account"]] += value
parent = node["parent_account"] parent = node["parent_account"]
if not parent == "": if parent != "":
return set_total( return set_total(
next(item for item in complete_list if item["account"] == parent), value, complete_list, totals next(item for item in complete_list if item["account"] == parent), value, complete_list, totals
) )

View File

@ -695,7 +695,7 @@ class GrossProfitGenerator(object):
def get_average_buying_rate(self, row, item_code): def get_average_buying_rate(self, row, item_code):
args = row args = row
if not item_code in self.average_buying_rate: if item_code not in self.average_buying_rate:
args.update( args.update(
{ {
"voucher_type": row.parenttype, "voucher_type": row.parenttype,

View File

@ -89,6 +89,8 @@ def _execute(filters=None, additional_table_columns=None):
"payable_account": inv.credit_to, "payable_account": inv.credit_to,
"mode_of_payment": inv.mode_of_payment, "mode_of_payment": inv.mode_of_payment,
"project": ", ".join(project) if inv.doctype == "Purchase Invoice" else inv.project, "project": ", ".join(project) if inv.doctype == "Purchase Invoice" else inv.project,
"bill_no": inv.bill_no,
"bill_date": inv.bill_date,
"remarks": inv.remarks, "remarks": inv.remarks,
"purchase_order": ", ".join(purchase_order), "purchase_order": ", ".join(purchase_order),
"purchase_receipt": ", ".join(purchase_receipt), "purchase_receipt": ", ".join(purchase_receipt),

View File

@ -154,7 +154,7 @@ def get_gle_map(documents):
) )
for d in gle: for d in gle:
if not d.voucher_no in gle_map: if d.voucher_no not in gle_map:
gle_map[d.voucher_no] = [d] gle_map[d.voucher_no] = [d]
else: else:
gle_map[d.voucher_no].append(d) gle_map[d.voucher_no].append(d)
@ -345,21 +345,16 @@ def get_tds_docs_query(filters, bank_accounts, tds_accounts):
if filters.get("party"): if filters.get("party"):
party = [filters.get("party")] party = [filters.get("party")]
query = query.where( jv_condition = gle.against.isin(party) | (
((gle.account.isin(tds_accounts) & gle.against.isin(party))) (gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party"))
| ((gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party")))
| gle.party.isin(party)
) )
else: else:
party = frappe.get_all(filters.get("party_type"), pluck="name") party = frappe.get_all(filters.get("party_type"), pluck="name")
query = query.where( jv_condition = gle.against.isin(party) | (
((gle.account.isin(tds_accounts) & gle.against.isin(party))) (gle.voucher_type == "Journal Entry")
| ( & ((gle.party_type == filters.get("party_type")) | (gle.party_type == ""))
(gle.voucher_type == "Journal Entry")
& ((gle.party_type == filters.get("party_type")) | (gle.party_type == ""))
)
| gle.party.isin(party)
) )
query = query.where((gle.account.isin(tds_accounts) & jv_condition) | gle.party.isin(party))
return query return query

View File

@ -642,6 +642,7 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
new_row.set("reference_name", d["against_voucher"]) new_row.set("reference_name", d["against_voucher"])
new_row.against_account = cstr(jv_detail.against_account) new_row.against_account = cstr(jv_detail.against_account)
new_row.against_account_link = cstr(jv_detail.against_account)
new_row.is_advance = cstr(jv_detail.is_advance) new_row.is_advance = cstr(jv_detail.is_advance)
new_row.docstatus = 1 new_row.docstatus = 1
@ -662,8 +663,10 @@ def update_reference_in_payment_entry(
"total_amount": d.grand_total, "total_amount": d.grand_total,
"outstanding_amount": d.outstanding_amount, "outstanding_amount": d.outstanding_amount,
"allocated_amount": d.allocated_amount, "allocated_amount": d.allocated_amount,
"exchange_rate": d.exchange_rate if d.exchange_gain_loss else payment_entry.get_exchange_rate(), "exchange_rate": d.exchange_rate
"exchange_gain_loss": d.exchange_gain_loss, if d.difference_amount is not None
else payment_entry.get_exchange_rate(),
"exchange_gain_loss": d.difference_amount,
"account": d.account, "account": d.account,
} }
@ -1062,11 +1065,11 @@ def get_outstanding_invoices(
if ( if (
min_outstanding min_outstanding
and max_outstanding and max_outstanding
and not (outstanding_amount >= min_outstanding and outstanding_amount <= max_outstanding) and (outstanding_amount < min_outstanding or outstanding_amount > max_outstanding)
): ):
continue continue
if not d.voucher_type == "Purchase Invoice" or d.voucher_no not in held_invoices: if d.voucher_type != "Purchase Invoice" or d.voucher_no not in held_invoices:
outstanding_invoices.append( outstanding_invoices.append(
frappe._dict( frappe._dict(
{ {

View File

@ -313,7 +313,7 @@ class Asset(AccountsController):
frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError) frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
if is_cwip_accounting_enabled(self.asset_category): if is_cwip_accounting_enabled(self.asset_category):
if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice): if not self.is_existing_asset and not self.purchase_receipt and not self.purchase_invoice:
frappe.throw( frappe.throw(
_("Please create purchase receipt or purchase invoice for the item {0}").format( _("Please create purchase receipt or purchase invoice for the item {0}").format(
self.item_code self.item_code
@ -689,7 +689,9 @@ class Asset(AccountsController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": cwip_account, "account": cwip_account,
"against_type": "Account",
"against": fixed_asset_account, "against": fixed_asset_account,
"against_link": fixed_asset_account,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"), "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date, "posting_date": self.available_for_use_date,
"credit": self.purchase_receipt_amount, "credit": self.purchase_receipt_amount,
@ -704,7 +706,9 @@ class Asset(AccountsController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": fixed_asset_account, "account": fixed_asset_account,
"against_type": "Account",
"against": cwip_account, "against": cwip_account,
"against_link": cwip_account,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"), "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date, "posting_date": self.available_for_use_date,
"debit": self.purchase_receipt_amount, "debit": self.purchase_receipt_amount,

View File

@ -251,7 +251,16 @@ class TestAsset(AssetSetup):
flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")), flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")),
0.0, 0.0,
), ),
("_Test Fixed Asset - _TC", 0.0, 100000.0), (
"_Test Fixed Asset - _TC",
0.0,
flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")),
),
(
"_Test Fixed Asset - _TC",
0.0,
flt(82000.0 - pro_rata_amount, asset.precision("gross_purchase_amount")),
),
( (
"_Test Gain/Loss on Asset Disposal - _TC", "_Test Gain/Loss on Asset Disposal - _TC",
flt(82000.0 - pro_rata_amount, asset.precision("gross_purchase_amount")), flt(82000.0 - pro_rata_amount, asset.precision("gross_purchase_amount")),

View File

@ -485,7 +485,9 @@ class AssetCapitalization(StockController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": account, "account": account,
"against_type": "Account",
"against": target_account, "against": target_account,
"against_link": target_account,
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"project": item_row.get("project") or self.get("project"), "project": item_row.get("project") or self.get("project"),
"remarks": self.get("remarks") or "Accounting Entry for Stock", "remarks": self.get("remarks") or "Accounting Entry for Stock",
@ -526,7 +528,9 @@ class AssetCapitalization(StockController):
self.set_consumed_asset_status(asset) self.set_consumed_asset_status(asset)
for gle in fixed_asset_gl_entries: for gle in fixed_asset_gl_entries:
gle["against_type"] = "Account"
gle["against"] = target_account gle["against"] = target_account
gle["against_link"] = target_account
gl_entries.append(self.get_gl_dict(gle, item=item)) gl_entries.append(self.get_gl_dict(gle, item=item))
target_against.add(gle["account"]) target_against.add(gle["account"])
@ -542,7 +546,9 @@ class AssetCapitalization(StockController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": item_row.expense_account, "account": item_row.expense_account,
"against_type": "Account",
"against": target_account, "against": target_account,
"against_link": target_account,
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"project": item_row.get("project") or self.get("project"), "project": item_row.get("project") or self.get("project"),
"remarks": self.get("remarks") or "Accounting Entry for Stock", "remarks": self.get("remarks") or "Accounting Entry for Stock",
@ -553,41 +559,46 @@ class AssetCapitalization(StockController):
) )
def get_gl_entries_for_target_item(self, gl_entries, target_against, precision): def get_gl_entries_for_target_item(self, gl_entries, target_against, precision):
if self.target_is_fixed_asset: for target_account in target_against:
# Capitalization if self.target_is_fixed_asset:
gl_entries.append( # Capitalization
self.get_gl_dict(
{
"account": self.target_fixed_asset_account,
"against": ", ".join(target_against),
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"debit": flt(self.total_value, precision),
"cost_center": self.get("cost_center"),
},
item=self,
)
)
else:
# Target Stock Item
sle_list = self.sle_map.get(self.name)
for sle in sle_list:
stock_value_difference = flt(sle.stock_value_difference, precision)
account = self.warehouse_account[sle.warehouse]["account"]
gl_entries.append( gl_entries.append(
self.get_gl_dict( self.get_gl_dict(
{ {
"account": account, "account": self.target_fixed_asset_account,
"against": ", ".join(target_against), "against_type": "Account",
"cost_center": self.cost_center, "against": target_account,
"project": self.get("project"), "against_link": target_account,
"remarks": self.get("remarks") or "Accounting Entry for Stock", "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"debit": stock_value_difference, "debit": flt(self.total_value, precision) / len(target_against),
"cost_center": self.get("cost_center"),
}, },
self.warehouse_account[sle.warehouse]["account_currency"],
item=self, item=self,
) )
) )
else:
# Target Stock Item
sle_list = self.sle_map.get(self.name)
for sle in sle_list:
stock_value_difference = flt(sle.stock_value_difference, precision)
account = self.warehouse_account[sle.warehouse]["account"]
gl_entries.append(
self.get_gl_dict(
{
"account": account,
"against_type": "Account",
"against": target_account,
"against_link": target_account,
"cost_center": self.cost_center,
"project": self.get("project"),
"remarks": self.get("remarks") or "Accounting Entry for Stock",
"debit": stock_value_difference / len(target_against),
},
self.warehouse_account[sle.warehouse]["account_currency"],
item=self,
)
)
def create_target_asset(self): def create_target_asset(self):
if ( if (

View File

@ -98,12 +98,12 @@ class TestAssetCapitalization(unittest.TestCase):
# Test General Ledger Entries # Test General Ledger Entries
expected_gle = { expected_gle = {
"_Test Fixed Asset - TCP1": 3000, "_Test Fixed Asset - TCP1": 2999.99,
"Expenses Included In Asset Valuation - TCP1": -1000, "Expenses Included In Asset Valuation - TCP1": -1000,
"_Test Warehouse - TCP1": -2000, "_Test Warehouse - TCP1": -2000,
"Round Off - TCP1": 0.01,
} }
actual_gle = get_actual_gle_dict(asset_capitalization.name) actual_gle = get_actual_gle_dict(asset_capitalization.name)
self.assertEqual(actual_gle, expected_gle) self.assertEqual(actual_gle, expected_gle)
# Test Stock Ledger Entries # Test Stock Ledger Entries
@ -189,9 +189,10 @@ class TestAssetCapitalization(unittest.TestCase):
# Test General Ledger Entries # Test General Ledger Entries
default_expense_account = frappe.db.get_value("Company", company, "default_expense_account") default_expense_account = frappe.db.get_value("Company", company, "default_expense_account")
expected_gle = { expected_gle = {
"_Test Fixed Asset - _TC": 3000, "_Test Fixed Asset - _TC": 2999.99,
"Expenses Included In Asset Valuation - _TC": -1000, "Expenses Included In Asset Valuation - _TC": -1000,
default_expense_account: -2000, default_expense_account: -2000,
"Round Off - _TC": 0.01,
} }
actual_gle = get_actual_gle_dict(asset_capitalization.name) actual_gle = get_actual_gle_dict(asset_capitalization.name)
@ -376,9 +377,10 @@ class TestAssetCapitalization(unittest.TestCase):
# Test General Ledger Entries # Test General Ledger Entries
expected_gle = { expected_gle = {
"_Test Warehouse - TCP1": consumed_asset_value_before_disposal,
"_Test Accumulated Depreciations - TCP1": accumulated_depreciation, "_Test Accumulated Depreciations - TCP1": accumulated_depreciation,
"_Test Fixed Asset - TCP1": -consumed_asset_purchase_value, "_Test Fixed Asset - TCP1": -consumed_asset_purchase_value,
"_Test Warehouse - TCP1": consumed_asset_value_before_disposal - 0.01,
"Round Off - TCP1": 0.01,
} }
actual_gle = get_actual_gle_dict(asset_capitalization.name) actual_gle = get_actual_gle_dict(asset_capitalization.name)
self.assertEqual(actual_gle, expected_gle) self.assertEqual(actual_gle, expected_gle)

View File

@ -340,6 +340,10 @@ class AssetDepreciationSchedule(Document):
n == 0 n == 0
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata) and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
and not self.opening_accumulated_depreciation and not self.opening_accumulated_depreciation
and get_updated_rate_of_depreciation_for_wdv_and_dd(
asset_doc, value_after_depreciation, row, False
)
== row.rate_of_depreciation
): ):
from_date = add_days( from_date = add_days(
asset_doc.available_for_use_date, -1 asset_doc.available_for_use_date, -1
@ -605,7 +609,9 @@ def get_depreciation_amount(
@erpnext.allow_regional @erpnext.allow_regional
def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb_row): def get_updated_rate_of_depreciation_for_wdv_and_dd(
asset, depreciable_value, fb_row, show_msg=True
):
return fb_row.rate_of_depreciation return fb_row.rate_of_depreciation

View File

@ -84,7 +84,7 @@ class AssetMovement(Document):
frappe.throw(_("Source and Target Location cannot be same")) frappe.throw(_("Source and Target Location cannot be same"))
if self.purpose == "Receipt": if self.purpose == "Receipt":
if not (d.source_location) and not (d.target_location or d.to_employee): if not (d.source_location) and not d.target_location and not d.to_employee:
frappe.throw( frappe.throw(
_("Target Location or To Employee is required while receiving Asset {0}").format(d.asset) _("Target Location or To Employee is required while receiving Asset {0}").format(d.asset)
) )

View File

@ -277,7 +277,9 @@ class AssetRepair(AccountsController):
"account": fixed_asset_account, "account": fixed_asset_account,
"debit": self.repair_cost, "debit": self.repair_cost,
"debit_in_account_currency": self.repair_cost, "debit_in_account_currency": self.repair_cost,
"against_type": "Account",
"against": pi_expense_account, "against": pi_expense_account,
"against_link": pi_expense_account,
"voucher_type": self.doctype, "voucher_type": self.doctype,
"voucher_no": self.name, "voucher_no": self.name,
"cost_center": self.cost_center, "cost_center": self.cost_center,
@ -296,7 +298,9 @@ class AssetRepair(AccountsController):
"account": pi_expense_account, "account": pi_expense_account,
"credit": self.repair_cost, "credit": self.repair_cost,
"credit_in_account_currency": self.repair_cost, "credit_in_account_currency": self.repair_cost,
"against_type": "Account",
"against": fixed_asset_account, "against": fixed_asset_account,
"against_link": fixed_asset_account,
"voucher_type": self.doctype, "voucher_type": self.doctype,
"voucher_no": self.name, "voucher_no": self.name,
"cost_center": self.cost_center, "cost_center": self.cost_center,
@ -330,7 +334,9 @@ class AssetRepair(AccountsController):
"account": item.expense_account or default_expense_account, "account": item.expense_account or default_expense_account,
"credit": item.amount, "credit": item.amount,
"credit_in_account_currency": item.amount, "credit_in_account_currency": item.amount,
"against_type": "Account",
"against": fixed_asset_account, "against": fixed_asset_account,
"against_link": fixed_asset_account,
"voucher_type": self.doctype, "voucher_type": self.doctype,
"voucher_no": self.name, "voucher_no": self.name,
"cost_center": self.cost_center, "cost_center": self.cost_center,
@ -347,7 +353,9 @@ class AssetRepair(AccountsController):
"account": fixed_asset_account, "account": fixed_asset_account,
"debit": item.amount, "debit": item.amount,
"debit_in_account_currency": item.amount, "debit_in_account_currency": item.amount,
"against_type": "Account",
"against": item.expense_account or default_expense_account, "against": item.expense_account or default_expense_account,
"against_link": item.expense_account or default_expense_account,
"voucher_type": self.doctype, "voucher_type": self.doctype,
"voucher_no": self.name, "voucher_no": self.name,
"cost_center": self.cost_center, "cost_center": self.cost_center,

View File

@ -214,7 +214,7 @@ frappe.ui.form.on("Purchase Order Item", {
} }
}, },
fg_item_qty: async function(frm, cdt, cdn) { qty: async function (frm, cdt, cdn) {
if (frm.doc.is_subcontracted && !frm.doc.is_old_subcontracting_flow) { if (frm.doc.is_subcontracted && !frm.doc.is_old_subcontracting_flow) {
var row = locals[cdt][cdn]; var row = locals[cdt][cdn];
@ -222,7 +222,7 @@ frappe.ui.form.on("Purchase Order Item", {
var result = await frm.events.get_subcontracting_boms_for_finished_goods(row.fg_item) var result = await frm.events.get_subcontracting_boms_for_finished_goods(row.fg_item)
if (result.message && row.item_code == result.message.service_item && row.uom == result.message.service_item_uom) { if (result.message && row.item_code == result.message.service_item && row.uom == result.message.service_item_uom) {
frappe.model.set_value(cdt, cdn, "qty", flt(row.fg_item_qty) * flt(result.message.conversion_factor)); frappe.model.set_value(cdt, cdn, "fg_item_qty", flt(row.qty) / flt(result.message.conversion_factor));
} }
} }
} }

View File

@ -119,6 +119,15 @@ class RequestforQuotation(BuyingController):
supplier.quote_status = "Pending" supplier.quote_status = "Pending"
self.send_to_supplier() self.send_to_supplier()
def before_print(self, settings=None):
"""Use the first suppliers data to render the print preview."""
if self.vendor or not self.suppliers:
# If a specific supplier is already set, via Tools > Download PDF,
# we don't want to override it.
return
self.update_supplier_part_no(self.suppliers[0].supplier)
def on_cancel(self): def on_cancel(self):
self.db_set("status", "Cancelled") self.db_set("status", "Cancelled")

View File

@ -114,7 +114,7 @@ def prepare_data(data, filters):
if filters.get("group_by_po"): if filters.get("group_by_po"):
po_name = row["purchase_order"] po_name = row["purchase_order"]
if not po_name in purchase_order_map: if po_name not in purchase_order_map:
# create an entry # create an entry
row_copy = copy.deepcopy(row) row_copy = copy.deepcopy(row)
purchase_order_map[po_name] = row_copy purchase_order_map[po_name] = row_copy

View File

@ -110,7 +110,7 @@ def prepare_data(data, filters):
for row in data: for row in data:
# item wise map for charts # item wise map for charts
if not row["item_code"] in item_qty_map: if row["item_code"] not in item_qty_map:
item_qty_map[row["item_code"]] = { item_qty_map[row["item_code"]] = {
"qty": flt(row["stock_qty"], precision), "qty": flt(row["stock_qty"], precision),
"stock_qty": flt(row["stock_qty"], precision), "stock_qty": flt(row["stock_qty"], precision),
@ -127,7 +127,7 @@ def prepare_data(data, filters):
if filters.get("group_by_mr"): if filters.get("group_by_mr"):
# consolidated material request map for group by filter # consolidated material request map for group by filter
if not row["material_request"] in material_request_map: if row["material_request"] not in material_request_map:
# create an entry with mr as key # create an entry with mr as key
row_copy = copy.deepcopy(row) row_copy = copy.deepcopy(row)
material_request_map[row["material_request"]] = row_copy material_request_map[row["material_request"]] = row_copy

View File

@ -126,7 +126,7 @@ def prepare_data(supplier_quotation_data, filters):
# map for chart preparation of the form {'supplier1': {'qty': 'price'}} # map for chart preparation of the form {'supplier1': {'qty': 'price'}}
supplier = data.get("supplier_name") supplier = data.get("supplier_name")
if filters.get("item_code"): if filters.get("item_code"):
if not supplier in supplier_qty_price_map: if supplier not in supplier_qty_price_map:
supplier_qty_price_map[supplier] = {} supplier_qty_price_map[supplier] = {}
supplier_qty_price_map[supplier][row["qty"]] = row["price"] supplier_qty_price_map[supplier][row["qty"]] = row["price"]
@ -169,7 +169,7 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map):
for supplier in suppliers: for supplier in suppliers:
entry = supplier_qty_price_map[supplier] entry = supplier_qty_price_map[supplier]
for qty in qty_list: for qty in qty_list:
if not qty in data_points_map: if qty not in data_points_map:
data_points_map[qty] = [] data_points_map[qty] = []
if qty in entry: if qty in entry:
data_points_map[qty].append(entry[qty]) data_points_map[qty].append(entry[qty])

View File

@ -166,6 +166,7 @@ class AccountsController(TransactionBase):
self.disable_pricing_rule_on_internal_transfer() self.disable_pricing_rule_on_internal_transfer()
self.disable_tax_included_prices_for_internal_transfer() self.disable_tax_included_prices_for_internal_transfer()
self.set_incoming_rate() self.set_incoming_rate()
self.init_internal_values()
if self.meta.get_field("currency"): if self.meta.get_field("currency"):
self.calculate_taxes_and_totals() self.calculate_taxes_and_totals()
@ -225,6 +226,16 @@ class AccountsController(TransactionBase):
self.set_total_in_words() self.set_total_in_words()
def init_internal_values(self):
# init all the internal values as 0 on sa
if self.docstatus.is_draft():
# TODO: Add all such pending values here
fields = ["billed_amt", "delivered_qty"]
for item in self.get("items"):
for field in fields:
if hasattr(item, field):
item.set(field, 0)
def before_cancel(self): def before_cancel(self):
validate_einvoice_fields(self) validate_einvoice_fields(self)
@ -292,6 +303,7 @@ class AccountsController(TransactionBase):
def on_trash(self): def on_trash(self):
self._remove_references_in_repost_doctypes() self._remove_references_in_repost_doctypes()
self._remove_references_in_unreconcile() self._remove_references_in_unreconcile()
self.remove_serial_and_batch_bundle()
# delete sl and gl entries on deletion of transaction # delete sl and gl entries on deletion of transaction
if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"): if frappe.db.get_single_value("Accounts Settings", "delete_linked_ledger_entries"):
@ -307,6 +319,15 @@ class AccountsController(TransactionBase):
(self.doctype, self.name), (self.doctype, self.name),
) )
def remove_serial_and_batch_bundle(self):
bundles = frappe.get_all(
"Serial and Batch Bundle",
filters={"voucher_type": self.doctype, "voucher_no": self.name, "docstatus": ("!=", 1)},
)
for bundle in bundles:
frappe.delete_doc("Serial and Batch Bundle", bundle.name)
def validate_deferred_income_expense_account(self): def validate_deferred_income_expense_account(self):
field_map = { field_map = {
"Sales Invoice": "deferred_revenue_account", "Sales Invoice": "deferred_revenue_account",
@ -1099,6 +1120,7 @@ class AccountsController(TransactionBase):
) )
credit_or_debit = "credit" if self.doctype == "Purchase Invoice" else "debit" credit_or_debit = "credit" if self.doctype == "Purchase Invoice" else "debit"
against_type = "Supplier" if self.doctype == "Purchase Invoice" else "Customer"
against = self.supplier if self.doctype == "Purchase Invoice" else self.customer against = self.supplier if self.doctype == "Purchase Invoice" else self.customer
if precision_loss: if precision_loss:
@ -1106,7 +1128,9 @@ class AccountsController(TransactionBase):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": round_off_account, "account": round_off_account,
"against_type": against_type,
"against": against, "against": against,
"against_link": against,
credit_or_debit: precision_loss, credit_or_debit: precision_loss,
"cost_center": round_off_cost_center "cost_center": round_off_cost_center
if self.use_company_roundoff_cost_center if self.use_company_roundoff_cost_center
@ -1455,11 +1479,13 @@ class AccountsController(TransactionBase):
if self.doctype == "Purchase Invoice": if self.doctype == "Purchase Invoice":
dr_or_cr = "credit" dr_or_cr = "credit"
rev_dr_cr = "debit" rev_dr_cr = "debit"
against_type = "Supplier"
supplier_or_customer = self.supplier supplier_or_customer = self.supplier
else: else:
dr_or_cr = "debit" dr_or_cr = "debit"
rev_dr_cr = "credit" rev_dr_cr = "credit"
against_type = "Customer"
supplier_or_customer = self.customer supplier_or_customer = self.customer
if enable_discount_accounting: if enable_discount_accounting:
@ -1484,7 +1510,9 @@ class AccountsController(TransactionBase):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": item.discount_account, "account": item.discount_account,
"against_type": against_type,
"against": supplier_or_customer, "against": supplier_or_customer,
"against_link": supplier_or_customer,
dr_or_cr: flt( dr_or_cr: flt(
discount_amount * self.get("conversion_rate"), item.precision("discount_amount") discount_amount * self.get("conversion_rate"), item.precision("discount_amount")
), ),
@ -1502,7 +1530,9 @@ class AccountsController(TransactionBase):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": income_or_expense_account, "account": income_or_expense_account,
"against_type": against_type,
"against": supplier_or_customer, "against": supplier_or_customer,
"against_link": supplier_or_customer,
rev_dr_cr: flt( rev_dr_cr: flt(
discount_amount * self.get("conversion_rate"), item.precision("discount_amount") discount_amount * self.get("conversion_rate"), item.precision("discount_amount")
), ),
@ -1525,7 +1555,9 @@ class AccountsController(TransactionBase):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": self.additional_discount_account, "account": self.additional_discount_account,
"against_type": against_type,
"against": supplier_or_customer, "against": supplier_or_customer,
"against_link": supplier_or_customer,
dr_or_cr: self.base_discount_amount, dr_or_cr: self.base_discount_amount,
"cost_center": self.cost_center or erpnext.get_default_cost_center(self.company), "cost_center": self.cost_center or erpnext.get_default_cost_center(self.company),
}, },

View File

@ -381,7 +381,11 @@ class BuyingController(SubcontractingController):
rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate")) rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
else: else:
field = "incoming_rate" if self.get("is_internal_supplier") else "rate" field = (
"incoming_rate"
if self.get("is_internal_supplier") and not self.doctype == "Purchase Order"
else "rate"
)
rate = flt( rate = flt(
frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field) frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
* (d.conversion_factor or 1), * (d.conversion_factor or 1),

View File

@ -56,10 +56,24 @@ def make_variant_based_on_manufacturer(template, manufacturer, manufacturer_part
copy_attributes_to_variant(template, variant) copy_attributes_to_variant(template, variant)
variant.manufacturer = manufacturer
variant.manufacturer_part_no = manufacturer_part_no
variant.item_code = append_number_if_name_exists("Item", template.name) variant.item_code = append_number_if_name_exists("Item", template.name)
variant.flags.ignore_mandatory = True
variant.save()
if not frappe.db.exists(
"Item Manufacturer", {"item_code": variant.name, "manufacturer": manufacturer}
):
manufacturer_doc = frappe.new_doc("Item Manufacturer")
manufacturer_doc.update(
{
"item_code": variant.name,
"manufacturer": manufacturer,
"manufacturer_part_no": manufacturer_part_no,
}
)
manufacturer_doc.flags.ignore_mandatory = True
manufacturer_doc.save(ignore_permissions=True)
return variant return variant

View File

@ -222,7 +222,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
searchfields = meta.get_search_fields() searchfields = meta.get_search_fields()
columns = "" columns = ""
extra_searchfields = [field for field in searchfields if not field in ["name", "description"]] extra_searchfields = [field for field in searchfields if field not in ["name", "description"]]
if extra_searchfields: if extra_searchfields:
columns += ", " + ", ".join(extra_searchfields) columns += ", " + ", ".join(extra_searchfields)
@ -233,8 +233,13 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
searchfields = searchfields + [ searchfields = searchfields + [
field field
for field in [searchfield or "name", "item_code", "item_group", "item_name"] for field in [
if not field in searchfields searchfield or "name",
"item_code",
"item_group",
"item_name",
]
if field not in searchfields
] ]
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
@ -872,7 +877,7 @@ def get_fields(doctype, fields=None):
meta = frappe.get_meta(doctype) meta = frappe.get_meta(doctype)
fields.extend(meta.get_search_fields()) fields.extend(meta.get_search_fields())
if meta.title_field and not meta.title_field.strip() in fields: if meta.title_field and meta.title_field.strip() not in fields:
fields.insert(1, meta.title_field.strip()) fields.insert(1, meta.title_field.strip())
return unique(fields) return unique(fields)

View File

@ -8,6 +8,8 @@ from frappe.model.meta import get_field_precision
from frappe.utils import flt, format_datetime, get_datetime from frappe.utils import flt, format_datetime, get_datetime
import erpnext import erpnext
from erpnext.stock.serial_batch_bundle import get_batches_from_bundle
from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle
from erpnext.stock.utils import get_incoming_rate from erpnext.stock.utils import get_incoming_rate
@ -69,8 +71,6 @@ def validate_return_against(doc):
def validate_returned_items(doc): def validate_returned_items(doc):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
valid_items = frappe._dict() valid_items = frappe._dict()
select_fields = "item_code, qty, stock_qty, rate, parenttype, conversion_factor" select_fields = "item_code, qty, stock_qty, rate, parenttype, conversion_factor"
@ -123,26 +123,6 @@ def validate_returned_items(doc):
) )
) )
elif ref.batch_no and d.batch_no not in ref.batch_no:
frappe.throw(
_("Row # {0}: Batch No must be same as {1} {2}").format(
d.idx, doc.doctype, doc.return_against
)
)
elif ref.serial_no:
if d.qty and not d.serial_no:
frappe.throw(_("Row # {0}: Serial No is mandatory").format(d.idx))
else:
serial_nos = get_serial_nos(d.serial_no)
for s in serial_nos:
if s not in ref.serial_no:
frappe.throw(
_("Row # {0}: Serial No {1} does not match with {2} {3}").format(
d.idx, s, doc.doctype, doc.return_against
)
)
if ( if (
warehouse_mandatory warehouse_mandatory
and not d.get("warehouse") and not d.get("warehouse")
@ -397,71 +377,92 @@ def make_return_doc(
else: else:
doc.run_method("calculate_taxes_and_totals") doc.run_method("calculate_taxes_and_totals")
def update_item(source_doc, target_doc, source_parent): def update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.serial_batch_bundle import SerialBatchCreation from erpnext.stock.serial_batch_bundle import SerialBatchCreation
target_doc.qty = -1 * source_doc.qty
item_details = frappe.get_cached_value(
"Item", source_doc.item_code, ["has_batch_no", "has_serial_no"], as_dict=1
)
returned_serial_nos = [] returned_serial_nos = []
if source_doc.get("serial_and_batch_bundle"): returned_batches = frappe._dict()
if item_details.has_serial_no: serial_and_batch_field = (
returned_serial_nos = get_returned_serial_nos(source_doc, source_parent) "serial_and_batch_bundle" if qty_field == "stock_qty" else "rejected_serial_and_batch_bundle"
)
old_serial_no_field = "serial_no" if qty_field == "stock_qty" else "rejected_serial_no"
old_batch_no_field = "batch_no"
type_of_transaction = "Inward" if (
if ( source_doc.get(serial_and_batch_field)
frappe.db.get_value( or source_doc.get(old_serial_no_field)
"Serial and Batch Bundle", source_doc.serial_and_batch_bundle, "type_of_transaction" or source_doc.get(old_batch_no_field)
) ):
== "Inward"
):
type_of_transaction = "Outward"
cls_obj = SerialBatchCreation(
{
"type_of_transaction": type_of_transaction,
"serial_and_batch_bundle": source_doc.serial_and_batch_bundle,
"returned_against": source_doc.name,
"item_code": source_doc.item_code,
"returned_serial_nos": returned_serial_nos,
}
)
cls_obj.duplicate_package()
if cls_obj.serial_and_batch_bundle:
target_doc.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle
if source_doc.get("rejected_serial_and_batch_bundle"):
if item_details.has_serial_no: if item_details.has_serial_no:
returned_serial_nos = get_returned_serial_nos( returned_serial_nos = get_returned_serial_nos(
source_doc, source_parent, serial_no_field="rejected_serial_and_batch_bundle" source_doc, source_parent, serial_no_field=serial_and_batch_field
)
else:
returned_batches = get_returned_batches(
source_doc, source_parent, batch_no_field=serial_and_batch_field
) )
type_of_transaction = "Inward" type_of_transaction = "Inward"
if ( if source_doc.get(serial_and_batch_field) and (
frappe.db.get_value( frappe.db.get_value(
"Serial and Batch Bundle", source_doc.rejected_serial_and_batch_bundle, "type_of_transaction" "Serial and Batch Bundle", source_doc.get(serial_and_batch_field), "type_of_transaction"
) )
== "Inward" == "Inward"
): ):
type_of_transaction = "Outward" type_of_transaction = "Outward"
elif source_parent.doctype in [
"Purchase Invoice",
"Purchase Receipt",
"Subcontracting Receipt",
]:
type_of_transaction = "Outward"
cls_obj = SerialBatchCreation( cls_obj = SerialBatchCreation(
{ {
"type_of_transaction": type_of_transaction, "type_of_transaction": type_of_transaction,
"serial_and_batch_bundle": source_doc.rejected_serial_and_batch_bundle, "serial_and_batch_bundle": source_doc.get(serial_and_batch_field),
"returned_against": source_doc.name, "returned_against": source_doc.name,
"item_code": source_doc.item_code, "item_code": source_doc.item_code,
"returned_serial_nos": returned_serial_nos, "returned_serial_nos": returned_serial_nos,
"voucher_type": source_parent.doctype,
"do_not_submit": True,
"warehouse": source_doc.warehouse,
"has_serial_no": item_details.has_serial_no,
"has_batch_no": item_details.has_batch_no,
} }
) )
cls_obj.duplicate_package() serial_nos = []
if cls_obj.serial_and_batch_bundle: batches = frappe._dict()
target_doc.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle if source_doc.get(old_batch_no_field):
batches = frappe._dict({source_doc.batch_no: source_doc.get(qty_field)})
elif source_doc.get(old_serial_no_field):
serial_nos = get_serial_nos(source_doc.get(old_serial_no_field))
elif source_doc.get(serial_and_batch_field):
if item_details.has_serial_no:
serial_nos = get_serial_nos_from_bundle(source_doc.get(serial_and_batch_field))
else:
batches = get_batches_from_bundle(source_doc.get(serial_and_batch_field))
if serial_nos:
cls_obj.serial_nos = sorted(list(set(serial_nos) - set(returned_serial_nos)))
elif batches:
for batch in batches:
if batch in returned_batches:
batches[batch] -= flt(returned_batches.get(batch))
cls_obj.batches = batches
if source_doc.get(serial_and_batch_field):
cls_obj.duplicate_package()
if cls_obj.serial_and_batch_bundle:
target_doc.set(serial_and_batch_field, cls_obj.serial_and_batch_bundle)
else:
target_doc.set(serial_and_batch_field, cls_obj.make_serial_and_batch_bundle().name)
def update_item(source_doc, target_doc, source_parent):
target_doc.qty = -1 * source_doc.qty
if doctype in ["Purchase Receipt", "Subcontracting Receipt"]: if doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
returned_qty_map = get_returned_qty_map_for_row( returned_qty_map = get_returned_qty_map_for_row(
source_parent.name, source_parent.supplier, source_doc.name, doctype source_parent.name, source_parent.supplier, source_doc.name, doctype
@ -561,6 +562,17 @@ def make_return_doc(
if default_warehouse_for_sales_return: if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return target_doc.warehouse = default_warehouse_for_sales_return
item_details = frappe.get_cached_value(
"Item", source_doc.item_code, ["has_batch_no", "has_serial_no"], as_dict=1
)
if not item_details.has_batch_no and not item_details.has_serial_no:
return
for qty_field in ["stock_qty", "rejected_qty"]:
if target_doc.get(qty_field):
update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field)
def update_terms(source_doc, target_doc, source_parent): def update_terms(source_doc, target_doc, source_parent):
target_doc.payment_amount = -source_doc.payment_amount target_doc.payment_amount = -source_doc.payment_amount
@ -716,6 +728,9 @@ def get_returned_serial_nos(
[parent_doc.doctype, "docstatus", "=", 1], [parent_doc.doctype, "docstatus", "=", 1],
] ]
if serial_no_field == "rejected_serial_and_batch_bundle":
filters.append([child_doc.doctype, "rejected_qty", ">", 0])
# Required for POS Invoice # Required for POS Invoice
if ignore_voucher_detail_no: if ignore_voucher_detail_no:
filters.append([child_doc.doctype, "name", "!=", ignore_voucher_detail_no]) filters.append([child_doc.doctype, "name", "!=", ignore_voucher_detail_no])
@ -723,9 +738,57 @@ def get_returned_serial_nos(
ids = [] ids = []
for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters): for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
ids.append(row.get("serial_and_batch_bundle")) ids.append(row.get("serial_and_batch_bundle"))
if row.get(old_field): if row.get(old_field) and not row.get(serial_no_field):
serial_nos.extend(get_serial_nos_from_serial_no(row.get(old_field))) serial_nos.extend(get_serial_nos_from_serial_no(row.get(old_field)))
serial_nos.extend(get_serial_nos(ids)) if ids:
serial_nos.extend(get_serial_nos(ids))
return serial_nos return serial_nos
def get_returned_batches(
child_doc, parent_doc, batch_no_field=None, ignore_voucher_detail_no=None
):
from erpnext.stock.serial_batch_bundle import get_batches_from_bundle
batches = frappe._dict()
old_field = "batch_no"
if not batch_no_field:
batch_no_field = "serial_and_batch_bundle"
return_ref_field = frappe.scrub(child_doc.doctype)
if child_doc.doctype == "Delivery Note Item":
return_ref_field = "dn_detail"
fields = [
f"`{'tab' + child_doc.doctype}`.`{batch_no_field}`",
f"`{'tab' + child_doc.doctype}`.`batch_no`",
f"`{'tab' + child_doc.doctype}`.`stock_qty`",
]
filters = [
[parent_doc.doctype, "return_against", "=", parent_doc.name],
[parent_doc.doctype, "is_return", "=", 1],
[child_doc.doctype, return_ref_field, "=", child_doc.name],
[parent_doc.doctype, "docstatus", "=", 1],
]
if batch_no_field == "rejected_serial_and_batch_bundle":
filters.append([child_doc.doctype, "rejected_qty", ">", 0])
# Required for POS Invoice
if ignore_voucher_detail_no:
filters.append([child_doc.doctype, "name", "!=", ignore_voucher_detail_no])
ids = []
for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
ids.append(row.get("serial_and_batch_bundle"))
if row.get(old_field) and not row.get(batch_no_field):
batches.setdefault(row.get(old_field), row.get("stock_qty"))
if ids:
batches.update(get_batches_from_bundle(ids))
return batches

View File

@ -12,7 +12,7 @@ from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
from erpnext.controllers.stock_controller import StockController from erpnext.controllers.stock_controller import StockController
from erpnext.stock.doctype.item.item import set_item_default from erpnext.stock.doctype.item.item import set_item_default
from erpnext.stock.get_item_details import get_bin_details, get_conversion_factor from erpnext.stock.get_item_details import get_bin_details, get_conversion_factor
from erpnext.stock.utils import get_incoming_rate from erpnext.stock.utils import get_incoming_rate, get_valuation_method
class SellingController(StockController): class SellingController(StockController):
@ -308,6 +308,8 @@ class SellingController(StockController):
"warehouse": p.warehouse or d.warehouse, "warehouse": p.warehouse or d.warehouse,
"item_code": p.item_code, "item_code": p.item_code,
"qty": flt(p.qty), "qty": flt(p.qty),
"serial_no": p.serial_no if self.docstatus == 2 else None,
"batch_no": p.batch_no if self.docstatus == 2 else None,
"uom": p.uom, "uom": p.uom,
"serial_and_batch_bundle": p.serial_and_batch_bundle "serial_and_batch_bundle": p.serial_and_batch_bundle
or get_serial_and_batch_bundle(p, self), or get_serial_and_batch_bundle(p, self),
@ -330,6 +332,8 @@ class SellingController(StockController):
"warehouse": d.warehouse, "warehouse": d.warehouse,
"item_code": d.item_code, "item_code": d.item_code,
"qty": d.stock_qty, "qty": d.stock_qty,
"serial_no": d.serial_no if self.docstatus == 2 else None,
"batch_no": d.batch_no if self.docstatus == 2 else None,
"uom": d.uom, "uom": d.uom,
"stock_uom": d.stock_uom, "stock_uom": d.stock_uom,
"conversion_factor": d.conversion_factor, "conversion_factor": d.conversion_factor,
@ -428,11 +432,13 @@ class SellingController(StockController):
items = self.get("items") + (self.get("packed_items") or []) items = self.get("items") + (self.get("packed_items") or [])
for d in items: for d in items:
if not self.get("return_against"): if not self.get("return_against") or (
get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
):
# Get incoming rate based on original item cost based on valuation method # Get incoming rate based on original item cost based on valuation method
qty = flt(d.get("stock_qty") or d.get("actual_qty")) qty = flt(d.get("stock_qty") or d.get("actual_qty"))
if not (self.get("is_return") and d.incoming_rate): if not d.incoming_rate:
d.incoming_rate = get_incoming_rate( d.incoming_rate = get_incoming_rate(
{ {
"item_code": d.item_code, "item_code": d.item_code,

View File

@ -162,7 +162,9 @@ class StockController(AccountsController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": warehouse_account[sle.warehouse]["account"], "account": warehouse_account[sle.warehouse]["account"],
"against_type": "Account",
"against": expense_account, "against": expense_account,
"against_link": expense_account,
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"project": item_row.project or self.get("project"), "project": item_row.project or self.get("project"),
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
@ -178,7 +180,9 @@ class StockController(AccountsController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": expense_account, "account": expense_account,
"against_type": "Account",
"against": warehouse_account[sle.warehouse]["account"], "against": warehouse_account[sle.warehouse]["account"],
"against_link": warehouse_account[sle.warehouse]["account"],
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": -1 * flt(sle.stock_value_difference, precision), "debit": -1 * flt(sle.stock_value_difference, precision),
@ -210,7 +214,9 @@ class StockController(AccountsController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": expense_account, "account": expense_account,
"against_type": "Account",
"against": warehouse_asset_account, "against": warehouse_asset_account,
"against_link": warehouse_asset_account,
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"project": item_row.project or self.get("project"), "project": item_row.project or self.get("project"),
"remarks": _("Rounding gain/loss Entry for Stock Transfer"), "remarks": _("Rounding gain/loss Entry for Stock Transfer"),
@ -226,7 +232,9 @@ class StockController(AccountsController):
self.get_gl_dict( self.get_gl_dict(
{ {
"account": warehouse_asset_account, "account": warehouse_asset_account,
"against_type": "Account",
"against": expense_account, "against": expense_account,
"against_link": expense_account,
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"remarks": _("Rounding gain/loss Entry for Stock Transfer"), "remarks": _("Rounding gain/loss Entry for Stock Transfer"),
"credit": sle_rounding_diff, "credit": sle_rounding_diff,
@ -455,6 +463,12 @@ class StockController(AccountsController):
sl_dict.update(args) sl_dict.update(args)
self.update_inventory_dimensions(d, sl_dict) self.update_inventory_dimensions(d, sl_dict)
if self.docstatus == 2:
# To handle denormalized serial no records, will br deprecated in v16
for field in ["serial_no", "batch_no"]:
if d.get(field):
sl_dict[field] = d.get(field)
return sl_dict return sl_dict
def update_inventory_dimensions(self, row, sl_dict) -> None: def update_inventory_dimensions(self, row, sl_dict) -> None:
@ -642,7 +656,7 @@ class StockController(AccountsController):
) )
qa_docstatus = frappe.db.get_value("Quality Inspection", row.quality_inspection, "docstatus") qa_docstatus = frappe.db.get_value("Quality Inspection", row.quality_inspection, "docstatus")
if not qa_docstatus == 1: if qa_docstatus != 1:
link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection) link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection)
msg = ( msg = (
f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}" f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}"
@ -826,6 +840,7 @@ class StockController(AccountsController):
credit, credit,
remarks, remarks,
against_account, against_account,
against_type="Account",
debit_in_account_currency=None, debit_in_account_currency=None,
credit_in_account_currency=None, credit_in_account_currency=None,
account_currency=None, account_currency=None,
@ -840,7 +855,9 @@ class StockController(AccountsController):
"cost_center": cost_center, "cost_center": cost_center,
"debit": debit, "debit": debit,
"credit": credit, "credit": credit,
"against_type": against_type,
"against": against_account, "against": against_account,
"against_link": against_account,
"remarks": remarks, "remarks": remarks,
} }

View File

@ -55,7 +55,7 @@ class Appointment(Document):
"Appointment", filters={"scheduled_time": self.scheduled_time} "Appointment", filters={"scheduled_time": self.scheduled_time}
) )
number_of_agents = frappe.db.get_single_value("Appointment Booking Settings", "number_of_agents") number_of_agents = frappe.db.get_single_value("Appointment Booking Settings", "number_of_agents")
if not number_of_agents == 0: if number_of_agents != 0:
if number_of_appointments_in_same_slot >= number_of_agents: if number_of_appointments_in_same_slot >= number_of_agents:
frappe.throw(_("Time slot is not available")) frappe.throw(_("Time slot is not available"))
# Link lead # Link lead
@ -110,7 +110,7 @@ class Appointment(Document):
cal_event.save(ignore_permissions=True) cal_event.save(ignore_permissions=True)
def set_verified(self, email): def set_verified(self, email):
if not email == self.customer_email: if email != self.customer_email:
frappe.throw(_("Email verification failed.")) frappe.throw(_("Email verification failed."))
# Create new lead # Create new lead
self.create_lead_and_link() self.create_lead_and_link()

View File

@ -516,7 +516,7 @@
"idx": 5, "idx": 5,
"image_field": "image", "image_field": "image",
"links": [], "links": [],
"modified": "2023-08-28 22:28:00.104413", "modified": "2023-12-01 18:46:49.468526",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Lead", "name": "Lead",
@ -577,6 +577,7 @@
], ],
"search_fields": "lead_name,lead_owner,status", "search_fields": "lead_name,lead_owner,status",
"sender_field": "email_id", "sender_field": "email_id",
"sender_name_field": "lead_name",
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",

View File

@ -16,6 +16,7 @@ from frappe.utils import comma_and, get_link_to_form, has_gravatar, validate_ema
from erpnext.accounts.party import set_taxes from erpnext.accounts.party import set_taxes
from erpnext.controllers.selling_controller import SellingController from erpnext.controllers.selling_controller import SellingController
from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_open_events from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_open_events
from erpnext.selling.doctype.customer.customer import parse_full_name
class Lead(SellingController, CRMNote): class Lead(SellingController, CRMNote):
@ -105,7 +106,7 @@ class Lead(SellingController, CRMNote):
if self.source == "Existing Customer" and self.customer: if self.source == "Existing Customer" and self.customer:
contact = frappe.db.get_value( contact = frappe.db.get_value(
"Dynamic Link", "Dynamic Link",
{"link_doctype": "Customer", "link_name": self.customer}, {"link_doctype": "Customer", "parenttype": "Contact", "link_name": self.customer},
"parent", "parent",
) )
if contact: if contact:
@ -113,6 +114,10 @@ class Lead(SellingController, CRMNote):
return return
self.contact_doc = self.create_contact() self.contact_doc = self.create_contact()
# leads created by email inbox only have the full name set
if self.lead_name and not any([self.first_name, self.middle_name, self.last_name]):
self.first_name, self.middle_name, self.last_name = parse_full_name(self.lead_name)
def after_insert(self): def after_insert(self):
self.link_to_contact() self.link_to_contact()

View File

@ -16,7 +16,7 @@ def validate_webhooks_request(doctype, hmac_key, secret_key="secret"):
hmac.new(settings.get(secret_key).encode("utf8"), frappe.request.data, hashlib.sha256).digest() hmac.new(settings.get(secret_key).encode("utf8"), frappe.request.data, hashlib.sha256).digest()
) )
if frappe.request.data and not sig == bytes(frappe.get_request_header(hmac_key).encode()): if frappe.request.data and sig != bytes(frappe.get_request_header(hmac_key).encode()):
frappe.throw(_("Unverified Webhook Data")) frappe.throw(_("Unverified Webhook Data"))
frappe.set_user(settings.modified_by) frappe.set_user(settings.modified_by)

View File

@ -637,6 +637,7 @@ additional_timeline_content = {
extend_bootinfo = [ extend_bootinfo = [
"erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes", "erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes",
"erpnext.startup.boot.bootinfo",
] ]

View File

@ -89,7 +89,7 @@ def make_order(source_name):
def update_item(source, target, source_parent): def update_item(source, target, source_parent):
target_qty = source.get("qty") - source.get("ordered_qty") target_qty = source.get("qty") - source.get("ordered_qty")
target.qty = target_qty if not flt(target_qty) < 0 else 0 target.qty = target_qty if flt(target_qty) >= 0 else 0
item = get_item_defaults(target.item_code, source_parent.company) item = get_item_defaults(target.item_code, source_parent.company)
if item: if item:
target.item_name = item.get("item_name") target.item_name = item.get("item_name")

View File

@ -1017,6 +1017,8 @@ def get_bom_item_rate(args, bom_doc):
item_doc = frappe.get_cached_doc("Item", args.get("item_code")) item_doc = frappe.get_cached_doc("Item", args.get("item_code"))
price_list_data = get_price_list_rate(bom_args, item_doc) price_list_data = get_price_list_rate(bom_args, item_doc)
rate = price_list_data.price_list_rate rate = price_list_data.price_list_rate
elif bom_doc.rm_cost_as_per == "Manual":
return
return flt(rate) return flt(rate)
@ -1381,7 +1383,7 @@ def get_bom_diff(bom1, bom2):
# check for deletions # check for deletions
for d in old_value: for d in old_value:
if not d.get(identifier) in new_row_by_identifier: if d.get(identifier) not in new_row_by_identifier:
out.removed.append([df.fieldname, d.as_dict()]) out.removed.append([df.fieldname, d.as_dict()])
return out return out
@ -1397,13 +1399,18 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
fields = ["name", "item_name", "item_group", "description"] fields = ["name", "item_name", "item_group", "description"]
fields.extend( fields.extend(
[field for field in searchfields if not field in ["name", "item_group", "description"]] [field for field in searchfields if field not in ["name", "item_group", "description"]]
) )
searchfields = searchfields + [ searchfields = searchfields + [
field field
for field in [searchfield or "name", "item_code", "item_group", "item_name"] for field in [
if not field in searchfields searchfield or "name",
"item_code",
"item_group",
"item_name",
]
if field not in searchfields
] ]
query_filters = {"disabled": 0, "ifnull(end_of_life, '3099-12-31')": (">", today())} query_filters = {"disabled": 0, "ifnull(end_of_life, '3099-12-31')": (">", today())}

View File

@ -583,6 +583,7 @@ class ProductionPlan(Document):
if close: if close:
self.db_set("status", "Closed") self.db_set("status", "Closed")
self.update_bin_qty()
return return
if self.total_produced_qty > 0: if self.total_produced_qty > 0:
@ -597,6 +598,9 @@ class ProductionPlan(Document):
if close is not None: if close is not None:
self.db_set("status", self.status) self.db_set("status", self.status)
if self.docstatus == 1 and self.status != "Completed":
self.update_bin_qty()
def update_ordered_status(self): def update_ordered_status(self):
update_status = False update_status = False
for d in self.po_items: for d in self.po_items:
@ -815,7 +819,7 @@ class ProductionPlan(Document):
key = "{}:{}:{}".format(item.sales_order, material_request_type, item_doc.customer or "") key = "{}:{}:{}".format(item.sales_order, material_request_type, item_doc.customer or "")
schedule_date = item.schedule_date or add_days(nowdate(), cint(item_doc.lead_time_days)) schedule_date = item.schedule_date or add_days(nowdate(), cint(item_doc.lead_time_days))
if not key in material_request_map: if key not in material_request_map:
# make a new MR for the combination # make a new MR for the combination
material_request_map[key] = frappe.new_doc("Material Request") material_request_map[key] = frappe.new_doc("Material Request")
material_request = material_request_map[key] material_request = material_request_map[key]
@ -1597,19 +1601,23 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company):
) )
locations = get_available_item_locations( locations = get_available_item_locations(
item.get("item_code"), warehouses, item.get("quantity"), company, ignore_validation=True item.get("item_code"),
warehouses,
item.get("quantity") * item.get("conversion_factor"),
company,
ignore_validation=True,
) )
required_qty = item.get("quantity") required_qty = item.get("quantity")
if item.get("conversion_factor") and item.get("purchase_uom") != item.get("stock_uom"):
# Convert qty to stock UOM
required_qty = required_qty * item.get("conversion_factor")
# get available material by transferring to production warehouse # get available material by transferring to production warehouse
for d in locations: for d in locations:
if required_qty <= 0: if required_qty <= 0:
return return
conversion_factor = 1.0
if purchase_uom != stock_uom and purchase_uom == item["uom"]:
conversion_factor = get_uom_conversion_factor(item["item_code"], item["uom"])
new_dict = copy.deepcopy(item) new_dict = copy.deepcopy(item)
quantity = required_qty if d.get("qty") > required_qty else d.get("qty") quantity = required_qty if d.get("qty") > required_qty else d.get("qty")
@ -1619,10 +1627,11 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company):
"material_request_type": "Material Transfer", "material_request_type": "Material Transfer",
"uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM "uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM
"from_warehouse": d.get("warehouse"), "from_warehouse": d.get("warehouse"),
"conversion_factor": 1.0,
} }
) )
required_qty -= quantity / conversion_factor required_qty -= quantity
new_mr_items.append(new_dict) new_mr_items.append(new_dict)
# raise purchase request for remaining qty # raise purchase request for remaining qty
@ -1634,7 +1643,7 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company):
if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"): if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"):
required_qty = ceil(required_qty) required_qty = ceil(required_qty)
item["quantity"] = required_qty item["quantity"] = required_qty / item.get("conversion_factor")
new_mr_items.append(item) new_mr_items.append(item)

View File

@ -1283,12 +1283,14 @@ class TestProductionPlan(FrappeTestCase):
for row in items: for row in items:
row = frappe._dict(row) row = frappe._dict(row)
if row.material_request_type == "Material Transfer": if row.material_request_type == "Material Transfer":
self.assertTrue(row.uom == row.stock_uom)
self.assertTrue(row.from_warehouse in [wh1, wh2]) self.assertTrue(row.from_warehouse in [wh1, wh2])
self.assertEqual(row.quantity, 2) self.assertEqual(row.quantity, 2)
if row.material_request_type == "Purchase": if row.material_request_type == "Purchase":
self.assertTrue(row.uom != row.stock_uom)
self.assertTrue(row.warehouse == mrp_warhouse) self.assertTrue(row.warehouse == mrp_warhouse)
self.assertEqual(row.quantity, 12) self.assertEqual(row.quantity, 12.0)
def test_mr_qty_for_same_rm_with_different_sub_assemblies(self): def test_mr_qty_for_same_rm_with_different_sub_assemblies(self):
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
@ -1404,6 +1406,99 @@ class TestProductionPlan(FrappeTestCase):
self.assertEqual(after_qty, before_qty) self.assertEqual(after_qty, before_qty)
def test_material_request_qty_purchase_and_material_transfer(self):
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name
bom_item = make_item(
properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1", "purchase_uom": "Nos"}
).name
store_warehouse = create_warehouse("Store Warehouse", company="_Test Company")
rm_warehouse = create_warehouse("RM Warehouse", company="_Test Company")
make_stock_entry(
item_code=bom_item,
qty=60,
target=store_warehouse,
rate=99,
)
if not frappe.db.exists("UOM Conversion Detail", {"parent": bom_item, "uom": "Nos"}):
doc = frappe.get_doc("Item", bom_item)
doc.append("uoms", {"uom": "Nos", "conversion_factor": 10})
doc.save()
make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC")
pln = create_production_plan(
item_code=fg_item, planned_qty=10, stock_uom="_Test UOM 1", do_not_submit=1
)
pln.for_warehouse = rm_warehouse
items = get_items_for_material_requests(
pln.as_dict(), warehouses=[{"warehouse": store_warehouse}]
)
for row in items:
self.assertEqual(row.get("quantity"), 10.0)
self.assertEqual(row.get("material_request_type"), "Material Transfer")
self.assertEqual(row.get("uom"), "_Test UOM 1")
self.assertEqual(row.get("from_warehouse"), store_warehouse)
self.assertEqual(row.get("conversion_factor"), 1.0)
items = get_items_for_material_requests(
pln.as_dict(), warehouses=[{"warehouse": pln.for_warehouse}]
)
for row in items:
self.assertEqual(row.get("quantity"), 1.0)
self.assertEqual(row.get("material_request_type"), "Purchase")
self.assertEqual(row.get("uom"), "Nos")
self.assertEqual(row.get("conversion_factor"), 10.0)
def test_unreserve_qty_on_closing_of_pp(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.utils import get_or_make_bin
fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name
rm_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name
store_warehouse = create_warehouse("Store Warehouse", company="_Test Company")
rm_warehouse = create_warehouse("RM Warehouse", company="_Test Company")
make_bom(item=fg_item, raw_materials=[rm_item], source_warehouse="_Test Warehouse - _TC")
pln = create_production_plan(
item_code=fg_item, planned_qty=10, stock_uom="_Test UOM 1", do_not_submit=1
)
pln.for_warehouse = rm_warehouse
mr_items = get_items_for_material_requests(pln.as_dict())
for d in mr_items:
pln.append("mr_items", d)
pln.save()
pln.submit()
bin_name = get_or_make_bin(rm_item, rm_warehouse)
before_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
pln.reload()
pln.set_status(close=True)
bin_name = get_or_make_bin(rm_item, rm_warehouse)
after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
self.assertAlmostEqual(after_qty, before_qty - 10)
pln.reload()
pln.set_status(close=False)
bin_name = get_or_make_bin(rm_item, rm_warehouse)
after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
self.assertAlmostEqual(after_qty, before_qty)
def create_production_plan(**args): def create_production_plan(**args):
""" """

View File

@ -930,7 +930,7 @@ class WorkOrder(Document):
validate_end_of_life(self.production_item) validate_end_of_life(self.production_item)
def validate_qty(self): def validate_qty(self):
if not self.qty > 0: if self.qty <= 0:
frappe.throw(_("Quantity to Manufacture must be greater than 0.")) frappe.throw(_("Quantity to Manufacture must be greater than 0."))
if ( if (
@ -957,7 +957,7 @@ class WorkOrder(Document):
max_qty = qty_dict.get("planned_qty", 0) + allowance_qty - qty_dict.get("ordered_qty", 0) max_qty = qty_dict.get("planned_qty", 0) + allowance_qty - qty_dict.get("ordered_qty", 0)
if not max_qty > 0: if max_qty <= 0:
frappe.throw( frappe.throw(
_("Cannot produce more item for {0}").format(self.production_item), OverProductionError _("Cannot produce more item for {0}").format(self.production_item), OverProductionError
) )
@ -968,7 +968,7 @@ class WorkOrder(Document):
) )
def validate_transfer_against(self): def validate_transfer_against(self):
if not self.docstatus == 1: if self.docstatus != 1:
# let user configure operations until they're ready to submit # let user configure operations until they're ready to submit
return return
if not self.operations: if not self.operations:
@ -981,7 +981,7 @@ class WorkOrder(Document):
def validate_operation_time(self): def validate_operation_time(self):
for d in self.operations: for d in self.operations:
if not d.time_in_mins > 0: if d.time_in_mins <= 0:
frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation)) frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation))
def update_required_items(self): def update_required_items(self):

View File

@ -351,6 +351,8 @@ erpnext.patches.v15_0.set_reserved_stock_in_bin
erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation
erpnext.patches.v14_0.update_zero_asset_quantity_field erpnext.patches.v14_0.update_zero_asset_quantity_field
execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction") execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction")
execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System Settings", "date_format"))
erpnext.patches.v15_0.create_advance_payment_status erpnext.patches.v15_0.create_advance_payment_status
# below migration patch should always run last # below migration patch should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index

View File

@ -10,7 +10,7 @@ def execute():
) )
if property_setter_name: if property_setter_name:
property_setter = frappe.get_doc("Property Setter", property_setter_name) property_setter = frappe.get_doc("Property Setter", property_setter_name)
if not "Completed" in property_setter.value: if "Completed" not in property_setter.value:
property_setter.value = property_setter.value + "\nCompleted" property_setter.value = property_setter.value + "\nCompleted"
property_setter.save() property_setter.save()

View File

@ -46,7 +46,7 @@ def execute():
{"response_time": response_time, "resolution_time": resolution_time}, {"response_time": response_time, "resolution_time": resolution_time},
) )
if priority.parenttype == "Service Level": if priority.parenttype == "Service Level":
if not priority.parent in priority_dict: if priority.parent not in priority_dict:
priority_dict[priority.parent] = [] priority_dict[priority.parent] = []
priority_dict[priority.parent].append(priority) priority_dict[priority.parent].append(priority)

View File

@ -3,6 +3,7 @@ import frappe
def execute(): def execute():
frappe.reload_doc("assets", "doctype", "Asset Depreciation Schedule") 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() assets = get_details_of_draft_or_submitted_depreciable_assets()
@ -86,6 +87,7 @@ def get_asset_finance_books_map():
afb.frequency_of_depreciation, afb.frequency_of_depreciation,
afb.rate_of_depreciation, afb.rate_of_depreciation,
afb.expected_value_after_useful_life, afb.expected_value_after_useful_life,
afb.daily_prorata_based,
afb.shift_based, afb.shift_based,
) )
.where(asset.docstatus < 2) .where(asset.docstatus < 2)

View File

@ -2,14 +2,6 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Homepage', { frappe.ui.form.on('Homepage', {
setup: function(frm) {
frm.fields_dict["products"].grid.get_field("item").get_query = function() {
return {
filters: {'published': 1}
}
}
},
refresh: function(frm) { refresh: function(frm) {
frm.add_custom_button(__('Set Meta Tags'), () => { frm.add_custom_button(__('Set Meta Tags'), () => {
frappe.utils.set_meta_tag('home'); frappe.utils.set_meta_tag('home');

View File

@ -50,7 +50,7 @@ def create_customer_or_supplier():
party = frappe.new_doc(doctype) party = frappe.new_doc(doctype)
fullname = frappe.utils.get_fullname(user) fullname = frappe.utils.get_fullname(user)
if not doctype == "Customer": if doctype != "Customer":
party.update( party.update(
{ {
"supplier_name": fullname, "supplier_name": fullname,

View File

@ -661,7 +661,7 @@ def set_project_status(project, status):
""" """
set status for project and all related tasks set status for project and all related tasks
""" """
if not status in ("Completed", "Cancelled"): if status not in ("Completed", "Cancelled"):
frappe.throw(_("Status must be Cancelled or Completed")) frappe.throw(_("Status must be Cancelled or Completed"))
project = frappe.get_doc("Project", project) project = frappe.get_doc("Project", project)

View File

@ -36,14 +36,14 @@ erpnext.buying = {
// no idea where me is coming from // no idea where me is coming from
if(this.frm.get_field('shipping_address')) { if(this.frm.get_field('shipping_address')) {
this.frm.set_query("shipping_address", function() { this.frm.set_query("shipping_address", () => {
if(me.frm.doc.customer) { if(this.frm.doc.customer) {
return { return {
query: 'frappe.contacts.doctype.address.address.address_query', query: 'frappe.contacts.doctype.address.address.address_query',
filters: { link_doctype: 'Customer', link_name: me.frm.doc.customer } filters: { link_doctype: 'Customer', link_name: this.frm.doc.customer }
}; };
} else } else
return erpnext.queries.company_address_query(me.frm.doc) return erpnext.queries.company_address_query(this.frm.doc)
}); });
} }
} }
@ -361,9 +361,14 @@ erpnext.buying = {
new erpnext.SerialBatchPackageSelector( new erpnext.SerialBatchPackageSelector(
me.frm, item, (r) => { me.frm, item, (r) => {
if (r) { if (r) {
let qty = Math.abs(r.total_qty);
if (doc.is_return) {
qty = qty * -1;
}
let update_values = { let update_values = {
"serial_and_batch_bundle": r.name, "serial_and_batch_bundle": r.name,
"qty": Math.abs(r.total_qty) "qty": qty
} }
if (r.warehouse) { if (r.warehouse) {
@ -396,9 +401,14 @@ erpnext.buying = {
new erpnext.SerialBatchPackageSelector( new erpnext.SerialBatchPackageSelector(
me.frm, item, (r) => { me.frm, item, (r) => {
if (r) { if (r) {
let qty = Math.abs(r.total_qty);
if (doc.is_return) {
qty = qty * -1;
}
let update_values = { let update_values = {
"serial_and_batch_bundle": r.name, "serial_and_batch_bundle": r.name,
"rejected_qty": Math.abs(r.total_qty) "rejected_qty": qty
} }
if (r.warehouse) { if (r.warehouse) {

View File

@ -380,6 +380,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
} }
scan_barcode() { scan_barcode() {
frappe.flags.dialog_set = false;
const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm}); const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
barcode_scanner.process_scan(); barcode_scanner.process_scan();
} }

View File

@ -2,10 +2,16 @@ frappe.provide("erpnext.financial_statements");
erpnext.financial_statements = { erpnext.financial_statements = {
"filters": get_filters(), "filters": get_filters(),
"formatter": function(value, row, column, data, default_formatter) { "formatter": function(value, row, column, data, default_formatter, filter) {
if (data && column.fieldname=="account") { if (data && column.fieldname=="account") {
value = data.account_name || value; value = data.account_name || value;
if (filter && filter?.text && filter?.type == "contains") {
if (!value.toLowerCase().includes(filter.text)) {
return value;
}
}
if (data.account) { if (data.account) {
column.link_onclick = column.link_onclick =
"erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")"; "erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";

View File

@ -8,7 +8,7 @@ $.extend(erpnext, {
if(!company && cur_frm) if(!company && cur_frm)
company = cur_frm.doc.company; company = cur_frm.doc.company;
if(company) if(company)
return frappe.get_doc(":Company", company).default_currency || frappe.boot.sysdefaults.currency; return frappe.get_doc(":Company", company)?.default_currency || frappe.boot.sysdefaults.currency;
else else
return frappe.boot.sysdefaults.currency; return frappe.boot.sysdefaults.currency;
}, },
@ -1077,7 +1077,7 @@ function set_time_to_resolve_and_response(frm, apply_sla_for_resolution) {
} }
function get_time_left(timestamp, agreement_status) { function get_time_left(timestamp, agreement_status) {
const diff = moment(timestamp).diff(moment()); const diff = moment(timestamp).diff(frappe.datetime.system_datetime(true));
const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : 'Failed'; const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : 'Failed';
let indicator = (diff_display == 'Failed' && agreement_status != 'Fulfilled') ? 'red' : 'green'; let indicator = (diff_display == 'Failed' && agreement_status != 'Fulfilled') ? 'red' : 'green';
return {'diff_display': diff_display, 'indicator': indicator}; return {'diff_display': diff_display, 'indicator': indicator};

View File

@ -114,13 +114,13 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
frappe.run_serially([ frappe.run_serially([
() => this.set_selector_trigger_flag(data), () => this.set_selector_trigger_flag(data),
() => this.set_serial_no(row, serial_no),
() => this.set_batch_no(row, batch_no),
() => this.set_barcode(row, barcode),
() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => { () => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
this.show_scan_message(row.idx, row.item_code, qty); this.show_scan_message(row.idx, row.item_code, qty);
}), }),
() => this.set_barcode_uom(row, uom), () => this.set_barcode_uom(row, uom),
() => this.set_serial_no(row, serial_no),
() => this.set_batch_no(row, batch_no),
() => this.set_barcode(row, barcode),
() => this.clean_up(), () => this.clean_up(),
() => this.revert_selector_flag(), () => this.revert_selector_flag(),
() => resolve(row) () => resolve(row)
@ -131,10 +131,10 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
// batch and serial selector is reduandant when all info can be added by scan // batch and serial selector is reduandant when all info can be added by scan
// this flag on item row is used by transaction.js to avoid triggering selector // this flag on item row is used by transaction.js to avoid triggering selector
set_selector_trigger_flag(data) { set_selector_trigger_flag(data) {
const {batch_no, serial_no, has_batch_no, has_serial_no} = data; const {has_batch_no, has_serial_no} = data;
const require_selecting_batch = has_batch_no && !batch_no; const require_selecting_batch = has_batch_no;
const require_selecting_serial = has_serial_no && !serial_no; const require_selecting_serial = has_serial_no;
if (!(require_selecting_batch || require_selecting_serial)) { if (!(require_selecting_batch || require_selecting_serial)) {
frappe.flags.hide_serial_batch_dialog = true; frappe.flags.hide_serial_batch_dialog = true;

View File

@ -317,9 +317,14 @@ erpnext.sales_common = {
new erpnext.SerialBatchPackageSelector( new erpnext.SerialBatchPackageSelector(
me.frm, item, (r) => { me.frm, item, (r) => {
if (r) { if (r) {
let qty = Math.abs(r.total_qty);
if (doc.is_return) {
qty = qty * -1;
}
frappe.model.set_value(item.doctype, item.name, { frappe.model.set_value(item.doctype, item.name, {
"serial_and_batch_bundle": r.name, "serial_and_batch_bundle": r.name,
"qty": Math.abs(r.total_qty) "qty": qty
}); });
} }
} }

View File

@ -31,19 +31,40 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
secondary_action: () => this.edit_full_form(), secondary_action: () => this.edit_full_form(),
}); });
this.dialog.set_value("qty", this.item.qty).then(() => {
if (this.item.serial_no) {
this.dialog.set_value("scan_serial_no", this.item.serial_no);
frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', '');
} else if (this.item.batch_no) {
this.dialog.set_value("scan_batch_no", this.item.batch_no);
frappe.model.set_value(this.item.doctype, this.item.name, 'batch_no', '');
}
});
this.dialog.show(); this.dialog.show();
this.$scan_btn = this.dialog.$wrapper.find(".link-btn"); this.$scan_btn = this.dialog.$wrapper.find(".link-btn");
this.$scan_btn.css("display", "inline"); this.$scan_btn.css("display", "inline");
let qty = this.item.stock_qty || this.item.transfer_qty || this.item.qty;
if (this.item?.is_rejected) {
qty = this.item.rejected_qty;
}
qty = Math.abs(qty);
if (qty > 0) {
this.dialog.set_value("qty", qty).then(() => {
if (this.item.serial_no && !this.item.serial_and_batch_bundle) {
let serial_nos = this.item.serial_no.split('\n');
if (serial_nos.length > 1) {
serial_nos.forEach(serial_no => {
this.dialog.fields_dict.entries.df.data.push({
serial_no: serial_no,
batch_no: this.item.batch_no
});
});
} else {
this.dialog.set_value("scan_serial_no", this.item.serial_no);
}
frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', '');
} else if (this.item.batch_no && !this.item.serial_and_batch_bundle) {
this.dialog.set_value("scan_batch_no", this.item.batch_no);
frappe.model.set_value(this.item.doctype, this.item.name, 'batch_no', '');
}
this.dialog.fields_dict.entries.grid.refresh();
});
}
} }
get_serial_no_filters() { get_serial_no_filters() {
@ -463,13 +484,13 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
} }
render_data() { render_data() {
if (!this.frm.is_new() && this.bundle) { if (this.bundle) {
frappe.call({ frappe.call({
method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_serial_batch_ledgers', method: 'erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_serial_batch_ledgers',
args: { args: {
item_code: this.item.item_code, item_code: this.item.item_code,
name: this.bundle, name: this.bundle,
voucher_no: this.item.parent, voucher_no: !this.frm.is_new() ? this.item.parent : "",
} }
}).then(r => { }).then(r => {
if (r.message) { if (r.message) {

View File

@ -153,7 +153,9 @@ def make_gl_entry(tax, gl_entries, doc, tax_accounts):
"account": tax.account_head, "account": tax.account_head,
"cost_center": tax.cost_center, "cost_center": tax.cost_center,
"posting_date": doc.posting_date, "posting_date": doc.posting_date,
"against_type": "Supplier",
"against": doc.supplier, "against": doc.supplier,
"against_link": doc.supplier,
dr_or_cr: tax.base_tax_amount_after_discount_amount, dr_or_cr: tax.base_tax_amount_after_discount_amount,
dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount
if account_currency == doc.company_currency if account_currency == doc.company_currency

View File

@ -240,7 +240,7 @@ def get_chart_data(data):
for row in data: for row in data:
item_key = row.get("item_code") item_key = row.get("item_code")
if not item_key in item_wise_sales_map: if item_key not in item_wise_sales_map:
item_wise_sales_map[item_key] = 0 item_wise_sales_map[item_key] = 0
item_wise_sales_map[item_key] = flt(item_wise_sales_map[item_key]) + flt(row.get("amount")) item_wise_sales_map[item_key] = flt(item_wise_sales_map[item_key]) + flt(row.get("amount"))

View File

@ -167,7 +167,7 @@ def prepare_data(data, so_elapsed_time, filters):
if filters.get("group_by_so"): if filters.get("group_by_so"):
so_name = row["sales_order"] so_name = row["sales_order"]
if not so_name in sales_order_map: if so_name not in sales_order_map:
# create an entry # create an entry
row_copy = copy.deepcopy(row) row_copy = copy.deepcopy(row)
sales_order_map[so_name] = row_copy sales_order_map[so_name] = row_copy

View File

@ -112,9 +112,9 @@ def create_transaction(doctype, company, start_date):
warehouse = get_warehouse(company) warehouse = get_warehouse(company)
if document_type == "Purchase Order": if document_type == "Purchase Order":
posting_date = get_random_date(start_date, 1, 30) posting_date = get_random_date(start_date, 1, 25)
else: else:
posting_date = get_random_date(start_date, 31, 364) posting_date = get_random_date(start_date, 31, 350)
doctype.update( doctype.update(
{ {

View File

@ -185,7 +185,10 @@ class AuthorizationControl(TransactionBase):
# Remove user specific rules from global authorization rules # Remove user specific rules from global authorization rules
for r in based_on: for r in based_on:
if r in final_based_on and not r in ["Itemwise Discount", "Item Group wise Discount"]: if r in final_based_on and r not in [
"Itemwise Discount",
"Item Group wise Discount",
]:
final_based_on.remove(r) final_based_on.remove(r)
# Check for authorization set on particular roles # Check for authorization set on particular roles
@ -213,7 +216,10 @@ class AuthorizationControl(TransactionBase):
# Remove role specific rules from global authorization rules # Remove role specific rules from global authorization rules
for r in based_on: for r in based_on:
if r in final_based_on and not r in ["Itemwise Discount", "Item Group wise Discount"]: if r in final_based_on and r not in [
"Itemwise Discount",
"Item Group wise Discount",
]:
final_based_on.remove(r) final_based_on.remove(r)
# Check for global authorization # Check for global authorization

View File

@ -1,21 +1,6 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
cur_frm.cscript.set_root_readonly(doc);
}
cur_frm.cscript.set_root_readonly = function(doc) {
// read-only for root customer group
if(!doc.parent_customer_group && !doc.__islocal) {
cur_frm.set_read_only();
cur_frm.set_intro(__("This is a root customer group and cannot be edited."));
} else {
cur_frm.set_intro(null);
}
}
frappe.ui.form.on("Customer Group", { frappe.ui.form.on("Customer Group", {
setup: function(frm){ setup: function(frm){
frm.set_query('parent_customer_group', function (doc) { frm.set_query('parent_customer_group', function (doc) {
@ -48,5 +33,17 @@ frappe.ui.form.on("Customer Group", {
} }
} }
}); });
} },
refresh: function(frm) {
frm.trigger("set_root_readonly");
},
set_root_readonly: function(frm) {
// read-only for root customer group
if(!frm.doc.parent_customer_group && !frm.doc.__islocal) {
frm.set_read_only();
frm.set_intro(__("This is a root customer group and cannot be edited."));
} else {
frm.set_intro(null);
}
},
}); });

View File

@ -44,7 +44,7 @@ class Department(NestedSet):
def before_rename(self, old, new, merge=False): def before_rename(self, old, new, merge=False):
# renaming consistency with abbreviation # renaming consistency with abbreviation
if not frappe.get_cached_value("Company", self.company, "abbr") in new: if frappe.get_cached_value("Company", self.company, "abbr") not in new:
new = get_abbreviated_name(new, self.company) new = get_abbreviated_name(new, self.company)
return new return new

View File

@ -689,7 +689,7 @@ class EmailDigest(Document):
] ]
def get_root_type_accounts(self, root_type): def get_root_type_accounts(self, root_type):
if not root_type in self._accounts: if root_type not in self._accounts:
self._accounts[root_type] = [ self._accounts[root_type] = [
d.name d.name
for d in frappe.db.get_all( for d in frappe.db.get_all(

View File

@ -187,7 +187,7 @@ class Employee(NestedSet):
throw(_("Please enter relieving date.")) throw(_("Please enter relieving date."))
def validate_for_enabled_user_id(self, enabled): def validate_for_enabled_user_id(self, enabled):
if not self.status == "Active": if self.status != "Active":
return return
if enabled is None: if enabled is None:

View File

@ -11,6 +11,7 @@ frappe.ui.form.on('Sales Person', {
frm.dashboard.add_indicator(__('Total Contribution Amount Against Invoices: {0}', frm.dashboard.add_indicator(__('Total Contribution Amount Against Invoices: {0}',
[format_currency(info.allocated_amount_against_invoice, info.currency)]), 'blue'); [format_currency(info.allocated_amount_against_invoice, info.currency)]), 'blue');
} }
frm.trigger("set_root_readonly");
}, },
setup: function(frm) { setup: function(frm) {
@ -27,22 +28,18 @@ frappe.ui.form.on('Sales Person', {
'Sales Order': () => frappe.new_doc("Sales Order") 'Sales Order': () => frappe.new_doc("Sales Order")
.then(() => frm.add_child("sales_team", {"sales_person": frm.doc.name})) .then(() => frm.add_child("sales_team", {"sales_person": frm.doc.name}))
} }
},
set_root_readonly: function(frm) {
// read-only for root
if(!frm.doc.parent_sales_person && !frm.doc.__islocal) {
frm.set_read_only();
frm.set_intro(__("This is a root sales person and cannot be edited."));
} else {
frm.set_intro(null);
}
} }
}); });
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
cur_frm.cscript.set_root_readonly(doc);
}
cur_frm.cscript.set_root_readonly = function(doc) {
// read-only for root
if(!doc.parent_sales_person && !doc.__islocal) {
cur_frm.set_read_only();
cur_frm.set_intro(__("This is a root sales person and cannot be edited."));
} else {
cur_frm.set_intro(null);
}
}
//get query select sales person //get query select sales person
cur_frm.fields_dict['parent_sales_person'].get_query = function(doc, cdt, cdn) { cur_frm.fields_dict['parent_sales_person'].get_query = function(doc, cdt, cdn) {

View File

@ -1,21 +1,6 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
cur_frm.cscript.refresh = function(doc) {
cur_frm.set_intro(doc.__islocal ? "" : __("There is nothing to edit."));
cur_frm.cscript.set_root_readonly(doc);
};
cur_frm.cscript.set_root_readonly = function(doc) {
// read-only for root customer group
if(!doc.parent_supplier_group && !doc.__islocal) {
cur_frm.set_read_only();
cur_frm.set_intro(__("This is a root supplier group and cannot be edited."));
} else {
cur_frm.set_intro(null);
}
};
frappe.ui.form.on("Supplier Group", { frappe.ui.form.on("Supplier Group", {
setup: function(frm){ setup: function(frm){
frm.set_query('parent_supplier_group', function (doc) { frm.set_query('parent_supplier_group', function (doc) {
@ -48,5 +33,17 @@ frappe.ui.form.on("Supplier Group", {
} }
} }
}); });
},
refresh: function(frm) {
frm.set_intro(frm.doc.__islocal ? "" : __("There is nothing to edit."));
frm.trigger("set_root_readonly");
},
set_root_readonly: function(frm) {
if(!frm.doc.parent_supplier_group && !frm.doc.__islocal) {
frm.trigger("set_read_only");
frm.set_intro(__("This is a root supplier group and cannot be edited."));
} else {
frm.set_intro(null);
}
} }
}); });

View File

@ -11,23 +11,22 @@ frappe.ui.form.on("Territory", {
} }
} }
}; };
},
refresh: function(frm) {
frm.trigger("set_root_readonly");
},
set_root_readonly: function(frm) {
// read-only for root territory
if(!frm.doc.parent_territory && !frm.doc.__islocal) {
frm.set_read_only();
frm.set_intro(__("This is a root territory and cannot be edited."));
} else {
frm.set_intro(null);
}
} }
}); });
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
cur_frm.cscript.set_root_readonly(doc);
}
cur_frm.cscript.set_root_readonly = function(doc) {
// read-only for root territory
if(!doc.parent_territory && !doc.__islocal) {
cur_frm.set_read_only();
cur_frm.set_intro(__("This is a root territory and cannot be edited."));
} else {
cur_frm.set_intro(null);
}
}
//get query select territory //get query select territory
cur_frm.fields_dict['parent_territory'].get_query = function(doc,cdt,cdn) { cur_frm.fields_dict['parent_territory'].get_query = function(doc,cdt,cdn) {
return{ return{

View File

@ -85,8 +85,6 @@ def set_single_defaults():
except frappe.ValidationError: except frappe.ValidationError:
pass pass
frappe.db.set_default("date_format", "dd-mm-yyyy")
setup_currency_exchange() setup_currency_exchange()
@ -197,7 +195,7 @@ def add_standard_navbar_items():
for item in erpnext_navbar_items: for item in erpnext_navbar_items:
current_labels = [item.get("item_label") for item in current_navbar_items] current_labels = [item.get("item_label") for item in current_navbar_items]
if not item.get("item_label") in current_labels: if item.get("item_label") not in current_labels:
navbar_settings.append("help_dropdown", item) navbar_settings.append("help_dropdown", item)
for item in current_navbar_items: for item in current_navbar_items:

Some files were not shown because too many files have changed in this diff Show More