Merge remote-tracking branch 'upstream/develop' into remove-unused-coa
This commit is contained in:
commit
5a5758423e
@ -5,7 +5,7 @@ fail_fast: false
|
|||||||
|
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.0.1
|
rev: v4.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
files: "erpnext.*"
|
files: "erpnext.*"
|
||||||
@ -15,6 +15,10 @@ repos:
|
|||||||
args: ['--branch', 'develop']
|
args: ['--branch', 'develop']
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- id: check-ast
|
- id: check-ast
|
||||||
|
- id: check-json
|
||||||
|
- id: check-toml
|
||||||
|
- id: check-yaml
|
||||||
|
- id: debug-statements
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-eslint
|
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||||
rev: v8.44.0
|
rev: v8.44.0
|
||||||
|
@ -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,
|
||||||
|
@ -77,7 +77,6 @@
|
|||||||
"1500 Fertige Erzeugnisse": {"account_type": "Stock"},
|
"1500 Fertige Erzeugnisse": {"account_type": "Stock"},
|
||||||
"1600 Handelswarenvorrat": {"account_type": "Stock Received But Not Billed"},
|
"1600 Handelswarenvorrat": {"account_type": "Stock Received But Not Billed"},
|
||||||
"1700 Noch nicht abrechenbare Leistungen": {"account_type": "Stock"},
|
"1700 Noch nicht abrechenbare Leistungen": {"account_type": "Stock"},
|
||||||
"1900 Wertberichtigungen": {"account_type": "Stock"},
|
|
||||||
"1800 Geleistete Anzahlungen": {"account_type": "Stock"},
|
"1800 Geleistete Anzahlungen": {"account_type": "Stock"},
|
||||||
"1900 Wertberichtigungen": {"account_type": "Stock"},
|
"1900 Wertberichtigungen": {"account_type": "Stock"},
|
||||||
"root_type": "Asset"
|
"root_type": "Asset"
|
||||||
|
@ -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"
|
||||||
@ -397,7 +399,9 @@
|
|||||||
"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": {}
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -17,10 +17,13 @@
|
|||||||
"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",
|
||||||
|
"voucher_subtype",
|
||||||
"voucher_no",
|
"voucher_no",
|
||||||
"voucher_detail_no",
|
"voucher_detail_no",
|
||||||
"project",
|
"project",
|
||||||
@ -128,6 +131,13 @@
|
|||||||
"label": "Credit Amount in Account Currency",
|
"label": "Credit Amount in Account Currency",
|
||||||
"options": "account_currency"
|
"options": "account_currency"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "against_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_filter": 1,
|
||||||
|
"label": "Against Type",
|
||||||
|
"options": "DocType"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "against",
|
"fieldname": "against",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
@ -136,14 +146,20 @@
|
|||||||
"oldfieldname": "against",
|
"oldfieldname": "against",
|
||||||
"oldfieldtype": "Text"
|
"oldfieldtype": "Text"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "against_link",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"in_filter": 1,
|
||||||
|
"label": "Against",
|
||||||
|
"options": "against_type"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "against_voucher_type",
|
"fieldname": "against_voucher_type",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Against Voucher Type",
|
"label": "Against Voucher Type",
|
||||||
"oldfieldname": "against_voucher_type",
|
"oldfieldname": "against_voucher_type",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"options": "DocType",
|
"options": "DocType"
|
||||||
"search_index": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "against_voucher",
|
"fieldname": "against_voucher",
|
||||||
@ -162,8 +178,7 @@
|
|||||||
"label": "Voucher Type",
|
"label": "Voucher Type",
|
||||||
"oldfieldname": "voucher_type",
|
"oldfieldname": "voucher_type",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "DocType",
|
"options": "DocType"
|
||||||
"search_index": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "voucher_no",
|
"fieldname": "voucher_no",
|
||||||
@ -280,13 +295,18 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Credit Amount in Transaction Currency",
|
"label": "Credit Amount in Transaction Currency",
|
||||||
"options": "transaction_currency"
|
"options": "transaction_currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "voucher_subtype",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Voucher Subtype"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-list",
|
"icon": "fa fa-list",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-16 21:38:44.072267",
|
"modified": "2023-12-18 15:38:14.006208",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "GL Entry",
|
"name": "GL Entry",
|
||||||
|
@ -39,6 +39,8 @@ class GLEntry(Document):
|
|||||||
account: DF.Link | None
|
account: DF.Link | None
|
||||||
account_currency: DF.Link | None
|
account_currency: DF.Link | None
|
||||||
against: DF.Text | None
|
against: DF.Text | None
|
||||||
|
against_link: DF.DynamicLink | None
|
||||||
|
against_type: DF.Link | None
|
||||||
against_voucher: DF.DynamicLink | None
|
against_voucher: DF.DynamicLink | None
|
||||||
against_voucher_type: DF.Link | None
|
against_voucher_type: DF.Link | None
|
||||||
company: DF.Link | None
|
company: DF.Link | None
|
||||||
@ -66,6 +68,7 @@ class GLEntry(Document):
|
|||||||
transaction_exchange_rate: DF.Float
|
transaction_exchange_rate: DF.Float
|
||||||
voucher_detail_no: DF.Data | None
|
voucher_detail_no: DF.Data | None
|
||||||
voucher_no: DF.DynamicLink | None
|
voucher_no: DF.DynamicLink | None
|
||||||
|
voucher_subtype: DF.SmallText | None
|
||||||
voucher_type: DF.Link | None
|
voucher_type: DF.Link | None
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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",
|
||||||
|
@ -304,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],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -750,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:
|
||||||
|
self.get_debited_credited_accounts()
|
||||||
|
if len(self.accounts_credited) > 1 and len(self.accounts_debited) > 1:
|
||||||
|
self.auto_set_against_accounts()
|
||||||
|
return
|
||||||
|
self.get_against_accounts()
|
||||||
|
|
||||||
|
def auto_set_against_accounts(self):
|
||||||
|
for i in range(0, len(self.accounts), 2):
|
||||||
|
acc = self.accounts[i]
|
||||||
|
against_acc = self.accounts[i + 1]
|
||||||
|
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"):
|
for d in self.get("accounts"):
|
||||||
if flt(d.debit) > 0:
|
if flt(d.debit) > 0:
|
||||||
accounts_debited.append(d.party or d.account)
|
self.accounts_debited.append(d)
|
||||||
if flt(d.credit) > 0:
|
elif flt(d.credit) > 0:
|
||||||
accounts_credited.append(d.party or d.account)
|
self.accounts_credited.append(d)
|
||||||
|
|
||||||
for d in self.get("accounts"):
|
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:
|
if flt(d.debit) > 0:
|
||||||
d.against_account = ", ".join(list(set(accounts_credited)))
|
against_account = self.accounts_credited[0]
|
||||||
if flt(d.credit) > 0:
|
elif flt(d.credit) > 0:
|
||||||
d.against_account = ", ".join(list(set(accounts_debited)))
|
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):
|
||||||
@ -967,20 +1031,19 @@ 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,
|
||||||
"against": d.against_account,
|
|
||||||
"debit": flt(d.debit, d.precision("debit")),
|
"debit": flt(d.debit, d.precision("debit")),
|
||||||
"credit": flt(d.credit, d.precision("credit")),
|
"credit": flt(d.credit, d.precision("credit")),
|
||||||
"account_currency": d.account_currency,
|
"account_currency": d.account_currency,
|
||||||
@ -1000,7 +1063,50 @@ class JournalEntry(AccountsController):
|
|||||||
},
|
},
|
||||||
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):
|
||||||
@ -1625,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()
|
||||||
|
@ -30,7 +30,6 @@
|
|||||||
"voucher_type": "Bank Entry"
|
"voucher_type": "Bank Entry"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"cheque_date": "2013-02-14",
|
"cheque_date": "2013-02-14",
|
||||||
"cheque_no": "33",
|
"cheque_no": "33",
|
||||||
@ -62,7 +61,6 @@
|
|||||||
"voucher_type": "Bank Entry"
|
"voucher_type": "Bank Entry"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"cheque_date": "2013-02-14",
|
"cheque_date": "2013-02-14",
|
||||||
"cheque_no": "33",
|
"cheque_no": "33",
|
||||||
@ -81,7 +79,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"account": "Sales - _TC",
|
"account": "Sales - _TC",
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
|
||||||
"credit_in_account_currency": 400.0,
|
"credit_in_account_currency": 400.0,
|
||||||
"debit_in_account_currency": 0.0,
|
"debit_in_account_currency": 0.0,
|
||||||
"doctype": "Journal Entry Account",
|
"doctype": "Journal Entry Account",
|
||||||
|
@ -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": [
|
||||||
{
|
{
|
||||||
@ -259,6 +261,13 @@
|
|||||||
"oldfieldtype": "Text",
|
"oldfieldtype": "Text",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "against_account_link",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Against Account",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "against_type"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "accounting_dimensions_section",
|
"fieldname": "accounting_dimensions_section",
|
||||||
@ -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",
|
||||||
|
@ -1061,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,
|
||||||
},
|
},
|
||||||
@ -1226,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,
|
||||||
@ -1241,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,
|
||||||
@ -1265,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
|
||||||
@ -1273,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
|
||||||
@ -1298,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
|
||||||
@ -1323,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,
|
||||||
|
@ -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",
|
||||||
|
@ -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))
|
||||||
|
@ -163,6 +163,18 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, __("Get Items From"));
|
}, __("Get Items From"));
|
||||||
|
|
||||||
|
if (!this.frm.doc.is_return) {
|
||||||
|
frappe.db.get_single_value("Buying Settings", "maintain_same_rate").then((value) => {
|
||||||
|
if (value) {
|
||||||
|
this.frm.doc.items.forEach((item) => {
|
||||||
|
this.frm.fields_dict.items.grid.update_docfield_property(
|
||||||
|
"rate", "read_only", (item.purchase_receipt && item.pr_detail)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted);
|
this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted);
|
||||||
|
|
||||||
|
@ -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
|
||||||
@ -1816,10 +1858,6 @@ def make_inter_company_sales_invoice(source_name, target_doc=None):
|
|||||||
return make_inter_company_transaction("Purchase Invoice", source_name, target_doc)
|
return make_inter_company_transaction("Purchase Invoice", source_name, target_doc)
|
||||||
|
|
||||||
|
|
||||||
def on_doctype_update():
|
|
||||||
frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"])
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_purchase_receipt(source_name, target_doc=None):
|
def make_purchase_receipt(source_name, target_doc=None):
|
||||||
def update_item(obj, target, source_parent):
|
def update_item(obj, target, source_parent):
|
||||||
|
@ -288,7 +288,6 @@
|
|||||||
"oldfieldname": "import_rate",
|
"oldfieldname": "import_rate",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "currency",
|
"options": "currency",
|
||||||
"read_only_depends_on": "eval: (!parent.is_return && doc.purchase_receipt && doc.pr_detail)",
|
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -919,7 +918,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-30 16:26:05.629780",
|
"modified": "2023-12-25 22:00:28.043555",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Item",
|
"name": "Purchase Invoice Item",
|
||||||
|
@ -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
|
||||||
@ -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")
|
||||||
),
|
),
|
||||||
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2540,10 +2575,6 @@ def get_loyalty_programs(customer):
|
|||||||
return lp_details
|
return lp_details
|
||||||
|
|
||||||
|
|
||||||
def on_doctype_update():
|
|
||||||
frappe.db.add_index("Sales Invoice", ["customer", "is_return", "return_against"])
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_invoice_discounting(source_name, target_doc=None):
|
def create_invoice_discounting(source_name, target_doc=None):
|
||||||
invoice = frappe.get_doc("Sales Invoice", source_name)
|
invoice = frappe.get_doc("Sales Invoice", source_name)
|
||||||
|
@ -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:
|
||||||
|
@ -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,6 +244,10 @@ 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):
|
||||||
|
if row.voucher_no == ple.voucher_no == ple.against_voucher_no:
|
||||||
|
row.paid -= amount
|
||||||
|
row.paid_in_account_currency -= amount_in_account_currency
|
||||||
|
else:
|
||||||
row.credit_note -= amount
|
row.credit_note -= amount
|
||||||
row.credit_note_in_account_currency -= amount_in_account_currency
|
row.credit_note_in_account_currency -= amount_in_account_currency
|
||||||
else:
|
else:
|
||||||
@ -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
|
||||||
|
@ -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]
|
||||||
|
@ -200,10 +200,10 @@ def get_gl_entries(filters, accounting_dimensions):
|
|||||||
"""
|
"""
|
||||||
select
|
select
|
||||||
name as gl_entry, posting_date, account, party_type, party,
|
name as gl_entry, posting_date, account, party_type, party,
|
||||||
voucher_type, voucher_no, {dimension_fields}
|
voucher_type, voucher_subtype, 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')")
|
||||||
|
|
||||||
@ -392,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
|
||||||
|
|
||||||
@ -605,6 +609,12 @@ def get_columns(filters):
|
|||||||
|
|
||||||
columns += [
|
columns += [
|
||||||
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 120},
|
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 120},
|
||||||
|
{
|
||||||
|
"label": _("Voucher Subtype"),
|
||||||
|
"fieldname": "voucher_subtype",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"width": 180,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": _("Voucher No"),
|
"label": _("Voucher No"),
|
||||||
"fieldname": "voucher_no",
|
"fieldname": "voucher_no",
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -35,6 +35,8 @@
|
|||||||
"purchase_receipt",
|
"purchase_receipt",
|
||||||
"purchase_invoice",
|
"purchase_invoice",
|
||||||
"available_for_use_date",
|
"available_for_use_date",
|
||||||
|
"total_asset_cost",
|
||||||
|
"additional_asset_cost",
|
||||||
"column_break_23",
|
"column_break_23",
|
||||||
"gross_purchase_amount",
|
"gross_purchase_amount",
|
||||||
"asset_quantity",
|
"asset_quantity",
|
||||||
@ -529,6 +531,22 @@
|
|||||||
"label": "Capitalized In",
|
"label": "Capitalized In",
|
||||||
"options": "Asset Capitalization",
|
"options": "Asset Capitalization",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.docstatus > 0",
|
||||||
|
"fieldname": "total_asset_cost",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Asset Cost",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.docstatus > 0",
|
||||||
|
"fieldname": "additional_asset_cost",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Additional Asset Cost",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 72,
|
"idx": 72,
|
||||||
@ -572,7 +590,7 @@
|
|||||||
"link_fieldname": "target_asset"
|
"link_fieldname": "target_asset"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-11-20 20:57:37.010467",
|
"modified": "2023-12-21 16:46:20.732869",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset",
|
"name": "Asset",
|
||||||
|
@ -50,6 +50,7 @@ class Asset(AccountsController):
|
|||||||
|
|
||||||
from erpnext.assets.doctype.asset_finance_book.asset_finance_book import AssetFinanceBook
|
from erpnext.assets.doctype.asset_finance_book.asset_finance_book import AssetFinanceBook
|
||||||
|
|
||||||
|
additional_asset_cost: DF.Currency
|
||||||
amended_from: DF.Link | None
|
amended_from: DF.Link | None
|
||||||
asset_category: DF.Link | None
|
asset_category: DF.Link | None
|
||||||
asset_name: DF.Data
|
asset_name: DF.Data
|
||||||
@ -111,6 +112,7 @@ class Asset(AccountsController):
|
|||||||
"Decapitalized",
|
"Decapitalized",
|
||||||
]
|
]
|
||||||
supplier: DF.Link | None
|
supplier: DF.Link | None
|
||||||
|
total_asset_cost: DF.Currency
|
||||||
total_number_of_depreciations: DF.Int
|
total_number_of_depreciations: DF.Int
|
||||||
value_after_depreciation: DF.Currency
|
value_after_depreciation: DF.Currency
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
@ -144,6 +146,7 @@ class Asset(AccountsController):
|
|||||||
).format(asset_depr_schedules_links)
|
).format(asset_depr_schedules_links)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.total_asset_cost = self.gross_purchase_amount
|
||||||
self.status = self.get_status()
|
self.status = self.get_status()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
@ -689,7 +692,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 +709,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,
|
||||||
|
@ -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")),
|
||||||
|
@ -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,15 +559,18 @@ 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):
|
||||||
|
for target_account in target_against:
|
||||||
if self.target_is_fixed_asset:
|
if self.target_is_fixed_asset:
|
||||||
# Capitalization
|
# Capitalization
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
"account": self.target_fixed_asset_account,
|
"account": self.target_fixed_asset_account,
|
||||||
"against": ", ".join(target_against),
|
"against_type": "Account",
|
||||||
|
"against": target_account,
|
||||||
|
"against_link": target_account,
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||||
"debit": flt(self.total_value, precision),
|
"debit": flt(self.total_value, precision) / len(target_against),
|
||||||
"cost_center": self.get("cost_center"),
|
"cost_center": self.get("cost_center"),
|
||||||
},
|
},
|
||||||
item=self,
|
item=self,
|
||||||
@ -578,11 +587,13 @@ class AssetCapitalization(StockController):
|
|||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
"account": account,
|
"account": account,
|
||||||
"against": ", ".join(target_against),
|
"against_type": "Account",
|
||||||
|
"against": target_account,
|
||||||
|
"against_link": target_account,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
"project": self.get("project"),
|
"project": self.get("project"),
|
||||||
"remarks": self.get("remarks") or "Accounting Entry for Stock",
|
"remarks": self.get("remarks") or "Accounting Entry for Stock",
|
||||||
"debit": stock_value_difference,
|
"debit": stock_value_difference / len(target_against),
|
||||||
},
|
},
|
||||||
self.warehouse_account[sle.warehouse]["account_currency"],
|
self.warehouse_account[sle.warehouse]["account_currency"],
|
||||||
item=self,
|
item=self,
|
||||||
|
@ -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)
|
||||||
|
@ -93,6 +93,10 @@ class AssetRepair(AccountsController):
|
|||||||
|
|
||||||
self.increase_asset_value()
|
self.increase_asset_value()
|
||||||
|
|
||||||
|
if self.capitalize_repair_cost:
|
||||||
|
self.asset_doc.total_asset_cost += self.repair_cost
|
||||||
|
self.asset_doc.additional_asset_cost += self.repair_cost
|
||||||
|
|
||||||
if self.get("stock_consumption"):
|
if self.get("stock_consumption"):
|
||||||
self.check_for_stock_items_and_warehouse()
|
self.check_for_stock_items_and_warehouse()
|
||||||
self.decrease_stock_quantity()
|
self.decrease_stock_quantity()
|
||||||
@ -128,6 +132,10 @@ class AssetRepair(AccountsController):
|
|||||||
|
|
||||||
self.decrease_asset_value()
|
self.decrease_asset_value()
|
||||||
|
|
||||||
|
if self.capitalize_repair_cost:
|
||||||
|
self.asset_doc.total_asset_cost -= self.repair_cost
|
||||||
|
self.asset_doc.additional_asset_cost -= self.repair_cost
|
||||||
|
|
||||||
if self.get("stock_consumption"):
|
if self.get("stock_consumption"):
|
||||||
self.increase_stock_quantity()
|
self.increase_stock_quantity()
|
||||||
if self.get("capitalize_repair_cost"):
|
if self.get("capitalize_repair_cost"):
|
||||||
@ -277,7 +285,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 +306,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 +342,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 +361,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,
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,8 +123,7 @@
|
|||||||
"oldfieldname": "item_code",
|
"oldfieldname": "item_code",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Item",
|
"options": "Item",
|
||||||
"reqd": 1,
|
"reqd": 1
|
||||||
"search_index": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "supplier_part_no",
|
"fieldname": "supplier_part_no",
|
||||||
|
@ -874,6 +874,7 @@ class AccountsController(TransactionBase):
|
|||||||
"project": self.get("project"),
|
"project": self.get("project"),
|
||||||
"post_net_value": args.get("post_net_value"),
|
"post_net_value": args.get("post_net_value"),
|
||||||
"voucher_detail_no": args.get("voucher_detail_no"),
|
"voucher_detail_no": args.get("voucher_detail_no"),
|
||||||
|
"voucher_subtype": self.get_voucher_subtype(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -929,6 +930,25 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
return gl_dict
|
return gl_dict
|
||||||
|
|
||||||
|
def get_voucher_subtype(self):
|
||||||
|
voucher_subtypes = {
|
||||||
|
"Journal Entry": "voucher_type",
|
||||||
|
"Payment Entry": "payment_type",
|
||||||
|
"Stock Entry": "stock_entry_type",
|
||||||
|
"Asset Capitalization": "entry_type",
|
||||||
|
}
|
||||||
|
if self.doctype in voucher_subtypes:
|
||||||
|
return self.get(voucher_subtypes[self.doctype])
|
||||||
|
elif self.doctype == "Purchase Receipt" and self.is_return:
|
||||||
|
return "Purchase Return"
|
||||||
|
elif self.doctype == "Delivery Note" and self.is_return:
|
||||||
|
return "Sales Return"
|
||||||
|
elif (self.doctype == "Sales Invoice" and self.is_return) or self.doctype == "Purchase Invoice":
|
||||||
|
return "Credit Note"
|
||||||
|
elif (self.doctype == "Purchase Invoice" and self.is_return) or self.doctype == "Sales Invoice":
|
||||||
|
return "Debit Note"
|
||||||
|
return self.doctype
|
||||||
|
|
||||||
def get_value_in_transaction_currency(self, account_currency, args, field):
|
def get_value_in_transaction_currency(self, account_currency, args, field):
|
||||||
if account_currency == self.get("currency"):
|
if account_currency == self.get("currency"):
|
||||||
return args.get(field + "_in_account_currency")
|
return args.get(field + "_in_account_currency")
|
||||||
@ -1120,6 +1140,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:
|
||||||
@ -1127,7 +1148,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
|
||||||
@ -1476,11 +1499,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:
|
||||||
@ -1505,7 +1530,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")
|
||||||
),
|
),
|
||||||
@ -1523,7 +1550,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")
|
||||||
),
|
),
|
||||||
@ -1546,7 +1575,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),
|
||||||
},
|
},
|
||||||
|
@ -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),
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -896,3 +896,31 @@ def get_payment_terms_for_references(doctype, txt, searchfield, start, page_len,
|
|||||||
as_list=1,
|
as_list=1,
|
||||||
)
|
)
|
||||||
return terms
|
return terms
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
|
def get_filtered_child_rows(doctype, txt, searchfield, start, page_len, filters) -> list:
|
||||||
|
table = frappe.qb.DocType(doctype)
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(table)
|
||||||
|
.select(
|
||||||
|
table.name,
|
||||||
|
Concat("#", table.idx, ", ", table.item_code),
|
||||||
|
)
|
||||||
|
.orderby(table.idx)
|
||||||
|
.offset(start)
|
||||||
|
.limit(page_len)
|
||||||
|
)
|
||||||
|
|
||||||
|
if filters:
|
||||||
|
for field, value in filters.items():
|
||||||
|
query = query.where(table[field] == value)
|
||||||
|
|
||||||
|
if txt:
|
||||||
|
txt += "%"
|
||||||
|
query = query.where(
|
||||||
|
((table.idx.like(txt.replace("#", ""))) | (table.item_code.like(txt))) | (table.name.like(txt))
|
||||||
|
)
|
||||||
|
|
||||||
|
return query.run(as_dict=False)
|
||||||
|
@ -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 (
|
||||||
frappe.db.get_value(
|
source_doc.get(serial_and_batch_field)
|
||||||
"Serial and Batch Bundle", source_doc.serial_and_batch_bundle, "type_of_transaction"
|
or source_doc.get(old_serial_no_field)
|
||||||
)
|
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,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
serial_nos = []
|
||||||
|
batches = frappe._dict()
|
||||||
|
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()
|
cls_obj.duplicate_package()
|
||||||
if cls_obj.serial_and_batch_bundle:
|
if cls_obj.serial_and_batch_bundle:
|
||||||
target_doc.serial_and_batch_bundle = 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)))
|
||||||
|
|
||||||
|
if ids:
|
||||||
serial_nos.extend(get_serial_nos(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
|
||||||
|
@ -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,15 @@ 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 or (
|
||||||
|
get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
|
||||||
|
):
|
||||||
d.incoming_rate = get_incoming_rate(
|
d.incoming_rate = get_incoming_rate(
|
||||||
{
|
{
|
||||||
"item_code": d.item_code,
|
"item_code": d.item_code,
|
||||||
|
@ -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:
|
||||||
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,6 +218,7 @@
|
|||||||
"options": "\nWork Order\nJob Card"
|
"options": "\nWork Order\nJob Card"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "1",
|
||||||
"fieldname": "conversion_rate",
|
"fieldname": "conversion_rate",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Conversion Rate",
|
"label": "Conversion Rate",
|
||||||
@ -636,7 +637,7 @@
|
|||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-07 11:38:08.152294",
|
"modified": "2023-12-26 19:34:08.159312",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM",
|
"name": "BOM",
|
||||||
|
@ -744,6 +744,9 @@ class BOM(WebsiteGenerator):
|
|||||||
base_total_rm_cost = 0
|
base_total_rm_cost = 0
|
||||||
|
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
|
if not d.is_stock_item and self.rm_cost_as_per == "Valuation Rate":
|
||||||
|
continue
|
||||||
|
|
||||||
old_rate = d.rate
|
old_rate = d.rate
|
||||||
if self.rm_cost_as_per != "Manual":
|
if self.rm_cost_as_per != "Manual":
|
||||||
d.rate = self.get_rm_rate(
|
d.rate = self.get_rm_rate(
|
||||||
@ -1017,6 +1020,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)
|
||||||
|
|
||||||
|
@ -698,6 +698,35 @@ class TestBOM(FrappeTestCase):
|
|||||||
bom.update_cost()
|
bom.update_cost()
|
||||||
self.assertFalse(bom.flags.cost_updated)
|
self.assertFalse(bom.flags.cost_updated)
|
||||||
|
|
||||||
|
def test_bom_with_service_item_cost(self):
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
|
||||||
|
rm_item = make_item(properties={"is_stock_item": 1, "valuation_rate": 1000.0}).name
|
||||||
|
|
||||||
|
service_item = make_item(properties={"is_stock_item": 0}).name
|
||||||
|
|
||||||
|
fg_item = make_item(properties={"is_stock_item": 1}).name
|
||||||
|
|
||||||
|
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||||
|
|
||||||
|
bom = make_bom(item=fg_item, raw_materials=[rm_item, service_item], do_not_save=True)
|
||||||
|
bom.rm_cost_as_per = "Valuation Rate"
|
||||||
|
|
||||||
|
for row in bom.items:
|
||||||
|
if row.item_code == service_item:
|
||||||
|
row.rate = 566.00
|
||||||
|
else:
|
||||||
|
row.rate = 800.00
|
||||||
|
|
||||||
|
bom.save()
|
||||||
|
|
||||||
|
for row in bom.items:
|
||||||
|
if row.item_code == service_item:
|
||||||
|
self.assertEqual(row.is_stock_item, 0)
|
||||||
|
self.assertEqual(row.rate, 566.00)
|
||||||
|
else:
|
||||||
|
self.assertEqual(row.is_stock_item, 1)
|
||||||
|
|
||||||
def test_do_not_include_manufacturing_and_fixed_items(self):
|
def test_do_not_include_manufacturing_and_fixed_items(self):
|
||||||
from erpnext.manufacturing.doctype.bom.bom import item_query
|
from erpnext.manufacturing.doctype.bom.bom import item_query
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
"bom_no",
|
"bom_no",
|
||||||
"source_warehouse",
|
"source_warehouse",
|
||||||
"allow_alternative_item",
|
"allow_alternative_item",
|
||||||
|
"is_stock_item",
|
||||||
"section_break_5",
|
"section_break_5",
|
||||||
"description",
|
"description",
|
||||||
"col_break1",
|
"col_break1",
|
||||||
@ -185,7 +186,7 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Rate",
|
"label": "Rate",
|
||||||
"options": "currency",
|
"options": "currency",
|
||||||
"read_only": 1,
|
"read_only_depends_on": "eval:doc.is_stock_item == 1",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -284,13 +285,21 @@
|
|||||||
"fieldname": "do_not_explode",
|
"fieldname": "do_not_explode",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Do Not Explode"
|
"label": "Do Not Explode"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fetch_from": "item_code.is_stock_item",
|
||||||
|
"fieldname": "is_stock_item",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Stock Item",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-14 18:35:51.378513",
|
"modified": "2023-12-20 16:21:55.477883",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM Item",
|
"name": "BOM Item",
|
||||||
|
@ -305,6 +305,8 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
frappe.throw(__("Select the Warehouse"));
|
frappe.throw(__("Select the Warehouse"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
frm.set_value("consider_minimum_order_qty", 0);
|
||||||
|
|
||||||
if (frm.doc.ignore_existing_ordered_qty) {
|
if (frm.doc.ignore_existing_ordered_qty) {
|
||||||
frm.events.get_items_for_material_requests(frm);
|
frm.events.get_items_for_material_requests(frm);
|
||||||
} else {
|
} else {
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
"material_request_planning",
|
"material_request_planning",
|
||||||
"include_non_stock_items",
|
"include_non_stock_items",
|
||||||
"include_subcontracted_items",
|
"include_subcontracted_items",
|
||||||
|
"consider_minimum_order_qty",
|
||||||
"include_safety_stock",
|
"include_safety_stock",
|
||||||
"ignore_existing_ordered_qty",
|
"ignore_existing_ordered_qty",
|
||||||
"column_break_25",
|
"column_break_25",
|
||||||
@ -423,13 +424,19 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Sub Assembly Warehouse",
|
"label": "Sub Assembly Warehouse",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "consider_minimum_order_qty",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Consider Minimum Order Qty"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-calendar",
|
"icon": "fa fa-calendar",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-03 14:08:11.928027",
|
"modified": "2023-12-26 16:31:13.740777",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Production Plan",
|
"name": "Production Plan",
|
||||||
|
@ -67,6 +67,7 @@ class ProductionPlan(Document):
|
|||||||
combine_items: DF.Check
|
combine_items: DF.Check
|
||||||
combine_sub_items: DF.Check
|
combine_sub_items: DF.Check
|
||||||
company: DF.Link
|
company: DF.Link
|
||||||
|
consider_minimum_order_qty: DF.Check
|
||||||
customer: DF.Link | None
|
customer: DF.Link | None
|
||||||
for_warehouse: DF.Link | None
|
for_warehouse: DF.Link | None
|
||||||
from_date: DF.Date | None
|
from_date: DF.Date | None
|
||||||
@ -583,6 +584,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 +599,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:
|
||||||
@ -1207,7 +1212,14 @@ def get_subitems(
|
|||||||
|
|
||||||
|
|
||||||
def get_material_request_items(
|
def get_material_request_items(
|
||||||
row, sales_order, company, ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict
|
doc,
|
||||||
|
row,
|
||||||
|
sales_order,
|
||||||
|
company,
|
||||||
|
ignore_existing_ordered_qty,
|
||||||
|
include_safety_stock,
|
||||||
|
warehouse,
|
||||||
|
bin_dict,
|
||||||
):
|
):
|
||||||
total_qty = row["qty"]
|
total_qty = row["qty"]
|
||||||
|
|
||||||
@ -1216,8 +1228,14 @@ def get_material_request_items(
|
|||||||
required_qty = total_qty
|
required_qty = total_qty
|
||||||
elif total_qty > bin_dict.get("projected_qty", 0):
|
elif total_qty > bin_dict.get("projected_qty", 0):
|
||||||
required_qty = total_qty - bin_dict.get("projected_qty", 0)
|
required_qty = total_qty - bin_dict.get("projected_qty", 0)
|
||||||
if required_qty > 0 and required_qty < row["min_order_qty"]:
|
|
||||||
|
if (
|
||||||
|
doc.get("consider_minimum_order_qty")
|
||||||
|
and required_qty > 0
|
||||||
|
and required_qty < row["min_order_qty"]
|
||||||
|
):
|
||||||
required_qty = row["min_order_qty"]
|
required_qty = row["min_order_qty"]
|
||||||
|
|
||||||
item_group_defaults = get_item_group_defaults(row.item_code, company)
|
item_group_defaults = get_item_group_defaults(row.item_code, company)
|
||||||
|
|
||||||
if not row["purchase_uom"]:
|
if not row["purchase_uom"]:
|
||||||
@ -1555,6 +1573,7 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
|
|||||||
|
|
||||||
if details.qty > 0:
|
if details.qty > 0:
|
||||||
items = get_material_request_items(
|
items = get_material_request_items(
|
||||||
|
doc,
|
||||||
details,
|
details,
|
||||||
sales_order,
|
sales_order,
|
||||||
company,
|
company,
|
||||||
|
@ -1458,6 +1458,70 @@ class TestProductionPlan(FrappeTestCase):
|
|||||||
self.assertEqual(row.get("uom"), "Nos")
|
self.assertEqual(row.get("uom"), "Nos")
|
||||||
self.assertEqual(row.get("conversion_factor"), 10.0)
|
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 test_min_order_qty_in_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}).name
|
||||||
|
rm_item = make_item(properties={"is_stock_item": 1, "min_order_qty": 1000}).name
|
||||||
|
|
||||||
|
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, do_not_submit=1)
|
||||||
|
|
||||||
|
pln.for_warehouse = rm_warehouse
|
||||||
|
mr_items = get_items_for_material_requests(pln.as_dict())
|
||||||
|
for d in mr_items:
|
||||||
|
self.assertEqual(d.get("quantity"), 10.0)
|
||||||
|
|
||||||
|
pln.consider_minimum_order_qty = 1
|
||||||
|
mr_items = get_items_for_material_requests(pln.as_dict())
|
||||||
|
for d in mr_items:
|
||||||
|
self.assertEqual(d.get("quantity"), 1000.0)
|
||||||
|
|
||||||
|
|
||||||
def create_production_plan(**args):
|
def create_production_plan(**args):
|
||||||
"""
|
"""
|
||||||
|
@ -352,5 +352,8 @@ 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"))
|
execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System Settings", "date_format"))
|
||||||
|
erpnext.patches.v14_0.update_total_asset_cost_field
|
||||||
# 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 # 2023-12-20
|
||||||
|
erpnext.patches.v14_0.set_maintain_stock_for_bom_item
|
||||||
|
19
erpnext/patches/v14_0/set_maintain_stock_for_bom_item.py
Normal file
19
erpnext/patches/v14_0/set_maintain_stock_for_bom_item.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
if not frappe.db.exists("BOM", {"docstatus": 1}):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Added is_stock_item to handle Read Only based on condition for the rate field
|
||||||
|
frappe.db.sql(
|
||||||
|
"""
|
||||||
|
UPDATE
|
||||||
|
`tabBOM Item` boi,
|
||||||
|
`tabItem` i
|
||||||
|
SET
|
||||||
|
boi.is_stock_item = i.is_stock_item
|
||||||
|
WHERE
|
||||||
|
boi.item_code = i.name
|
||||||
|
"""
|
||||||
|
)
|
17
erpnext/patches/v14_0/update_total_asset_cost_field.py
Normal file
17
erpnext/patches/v14_0/update_total_asset_cost_field.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
asset = frappe.qb.DocType("Asset")
|
||||||
|
frappe.qb.update(asset).set(asset.total_asset_cost, asset.gross_purchase_amount).run()
|
||||||
|
|
||||||
|
asset_repair_list = frappe.db.get_all(
|
||||||
|
"Asset Repair",
|
||||||
|
filters={"docstatus": 1, "repair_status": "Completed", "capitalize_repair_cost": 1},
|
||||||
|
fields=["asset", "repair_cost"],
|
||||||
|
)
|
||||||
|
|
||||||
|
for asset_repair in asset_repair_list:
|
||||||
|
frappe.qb.update(asset).set(
|
||||||
|
asset.total_asset_cost, asset.total_asset_cost + asset_repair.repair_cost
|
||||||
|
).where(asset.name == asset_repair.asset).run()
|
@ -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) {
|
||||||
|
@ -184,6 +184,12 @@ erpnext.sales_common = {
|
|||||||
refresh_field("incentives",row.name,row.parentfield);
|
refresh_field("incentives",row.name,row.parentfield);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warehouse(doc, cdt, cdn) {
|
||||||
|
if (doc.docstatus === 0 && doc.is_return && !doc.return_against) {
|
||||||
|
frappe.model.set_value(cdt, cdn, "incoming_rate", 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
toggle_editable_price_list_rate() {
|
toggle_editable_price_list_rate() {
|
||||||
var df = frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "price_list_rate", this.frm.doc.name);
|
var df = frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "price_list_rate", this.frm.doc.name);
|
||||||
var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate"));
|
var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate"));
|
||||||
@ -317,9 +323,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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,22 +32,39 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.dialog.show();
|
this.dialog.show();
|
||||||
|
this.$scan_btn = this.dialog.$wrapper.find(".link-btn");
|
||||||
|
this.$scan_btn.css("display", "inline");
|
||||||
|
|
||||||
let qty = this.item.stock_qty || this.item.transfer_qty || this.item.qty;
|
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(() => {
|
this.dialog.set_value("qty", qty).then(() => {
|
||||||
if (this.item.serial_no) {
|
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);
|
this.dialog.set_value("scan_serial_no", this.item.serial_no);
|
||||||
|
}
|
||||||
frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', '');
|
frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', '');
|
||||||
} else if (this.item.batch_no) {
|
} else if (this.item.batch_no && !this.item.serial_and_batch_bundle) {
|
||||||
this.dialog.set_value("scan_batch_no", 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', '');
|
frappe.model.set_value(this.item.doctype, this.item.name, 'batch_no', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dialog.fields_dict.entries.grid.refresh();
|
this.dialog.fields_dict.entries.grid.refresh();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
this.$scan_btn = this.dialog.$wrapper.find(".link-btn");
|
|
||||||
this.$scan_btn.css("display", "inline");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get_serial_no_filters() {
|
get_serial_no_filters() {
|
||||||
@ -467,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) {
|
||||||
|
@ -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
|
||||||
|
@ -182,7 +182,7 @@ frappe.ui.form.on("Sales Order", {
|
|||||||
create_stock_reservation_entries(frm) {
|
create_stock_reservation_entries(frm) {
|
||||||
const dialog = new frappe.ui.Dialog({
|
const dialog = new frappe.ui.Dialog({
|
||||||
title: __("Stock Reservation"),
|
title: __("Stock Reservation"),
|
||||||
size: "large",
|
size: "extra-large",
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
fieldname: "set_warehouse",
|
fieldname: "set_warehouse",
|
||||||
@ -207,6 +207,50 @@ frappe.ui.form.on("Sales Order", {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{fieldtype: "Column Break"},
|
{fieldtype: "Column Break"},
|
||||||
|
{
|
||||||
|
fieldname: "add_item",
|
||||||
|
fieldtype: "Link",
|
||||||
|
label: __("Add Item"),
|
||||||
|
options: "Sales Order Item",
|
||||||
|
get_query: () => {
|
||||||
|
return {
|
||||||
|
query: "erpnext.controllers.queries.get_filtered_child_rows",
|
||||||
|
filters: {
|
||||||
|
"parenttype": frm.doc.doctype,
|
||||||
|
"parent": frm.doc.name,
|
||||||
|
"reserve_stock": 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onchange: () => {
|
||||||
|
let sales_order_item = dialog.get_value("add_item");
|
||||||
|
|
||||||
|
if (sales_order_item) {
|
||||||
|
frm.doc.items.forEach(item => {
|
||||||
|
if (item.name === sales_order_item) {
|
||||||
|
let unreserved_qty = (flt(item.stock_qty) - (item.stock_reserved_qty ? flt(item.stock_reserved_qty) : (flt(item.delivered_qty) * flt(item.conversion_factor)))) / flt(item.conversion_factor);
|
||||||
|
|
||||||
|
if (unreserved_qty > 0) {
|
||||||
|
dialog.fields_dict.items.df.data.forEach((row) => {
|
||||||
|
if (row.sales_order_item === sales_order_item) {
|
||||||
|
unreserved_qty -= row.qty_to_reserve;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.fields_dict.items.df.data.push({
|
||||||
|
'sales_order_item': item.name,
|
||||||
|
'item_code': item.item_code,
|
||||||
|
'warehouse': dialog.get_value("set_warehouse") || item.warehouse,
|
||||||
|
'qty_to_reserve': Math.max(unreserved_qty, 0)
|
||||||
|
});
|
||||||
|
dialog.fields_dict.items.grid.refresh();
|
||||||
|
dialog.set_value("add_item", undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{fieldtype: "Section Break"},
|
{fieldtype: "Section Break"},
|
||||||
{
|
{
|
||||||
fieldname: "items",
|
fieldname: "items",
|
||||||
@ -218,10 +262,34 @@ frappe.ui.form.on("Sales Order", {
|
|||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
fieldname: "sales_order_item",
|
fieldname: "sales_order_item",
|
||||||
fieldtype: "Data",
|
fieldtype: "Link",
|
||||||
label: __("Sales Order Item"),
|
label: __("Sales Order Item"),
|
||||||
|
options: "Sales Order Item",
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
read_only: 1,
|
in_list_view: 1,
|
||||||
|
get_query: () => {
|
||||||
|
return {
|
||||||
|
query: "erpnext.controllers.queries.get_filtered_child_rows",
|
||||||
|
filters: {
|
||||||
|
"parenttype": frm.doc.doctype,
|
||||||
|
"parent": frm.doc.name,
|
||||||
|
"reserve_stock": 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onchange: (event) => {
|
||||||
|
if (event) {
|
||||||
|
let name = $(event.currentTarget).closest(".grid-row").attr("data-name");
|
||||||
|
let item_row = dialog.fields_dict.items.grid.grid_rows_by_docname[name].doc;
|
||||||
|
|
||||||
|
frm.doc.items.forEach(item => {
|
||||||
|
if (item.name === item_row.sales_order_item) {
|
||||||
|
item_row.item_code = item.item_code;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialog.fields_dict.items.grid.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "item_code",
|
fieldname: "item_code",
|
||||||
@ -284,14 +352,14 @@ frappe.ui.form.on("Sales Order", {
|
|||||||
|
|
||||||
frm.doc.items.forEach(item => {
|
frm.doc.items.forEach(item => {
|
||||||
if (item.reserve_stock) {
|
if (item.reserve_stock) {
|
||||||
let unreserved_qty = (flt(item.stock_qty) - (item.stock_reserved_qty ? flt(item.stock_reserved_qty) : (flt(item.delivered_qty) * flt(item.conversion_factor))))
|
let unreserved_qty = (flt(item.stock_qty) - (item.stock_reserved_qty ? flt(item.stock_reserved_qty) : (flt(item.delivered_qty) * flt(item.conversion_factor)))) / flt(item.conversion_factor);
|
||||||
|
|
||||||
if (unreserved_qty > 0) {
|
if (unreserved_qty > 0) {
|
||||||
dialog.fields_dict.items.df.data.push({
|
dialog.fields_dict.items.df.data.push({
|
||||||
'sales_order_item': item.name,
|
'sales_order_item': item.name,
|
||||||
'item_code': item.item_code,
|
'item_code': item.item_code,
|
||||||
'warehouse': item.warehouse,
|
'warehouse': item.warehouse,
|
||||||
'qty_to_reserve': (unreserved_qty / flt(item.conversion_factor))
|
'qty_to_reserve': unreserved_qty
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -520,7 +520,7 @@ erpnext.PointOfSale.ItemCart = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render_taxes(taxes) {
|
render_taxes(taxes) {
|
||||||
if (taxes.length) {
|
if (taxes && taxes.length) {
|
||||||
const currency = this.events.get_frm().doc.currency;
|
const currency = this.events.get_frm().doc.currency;
|
||||||
const taxes_html = taxes.map(t => {
|
const taxes_html = taxes.map(t => {
|
||||||
if (t.tax_amount_after_discount_amount == 0.0) return;
|
if (t.tax_amount_after_discount_amount == 0.0) return;
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _, qb
|
||||||
|
from frappe.query_builder import Criterion
|
||||||
|
|
||||||
from erpnext import get_default_company
|
from erpnext import get_default_company
|
||||||
from erpnext.accounts.party import get_party_details
|
from erpnext.accounts.party import get_party_details
|
||||||
from erpnext.stock.get_item_details import get_price_list_rate_for
|
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
@ -50,6 +50,42 @@ def get_columns(filters=None):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_item_prices(
|
||||||
|
customer: str = None, price_list: str = None, selling_price_list: str = None, items: list = None
|
||||||
|
):
|
||||||
|
price_list_map = frappe._dict()
|
||||||
|
ip = qb.DocType("Item Price")
|
||||||
|
and_conditions = []
|
||||||
|
or_conditions = []
|
||||||
|
if items:
|
||||||
|
and_conditions.append(ip.item_code.isin([x.item_code for x in items]))
|
||||||
|
and_conditions.append(ip.selling == True)
|
||||||
|
|
||||||
|
or_conditions.append(ip.customer == None)
|
||||||
|
or_conditions.append(ip.price_list == None)
|
||||||
|
|
||||||
|
if customer:
|
||||||
|
or_conditions.append(ip.customer == customer)
|
||||||
|
|
||||||
|
if price_list:
|
||||||
|
or_conditions.append(ip.price_list == price_list)
|
||||||
|
|
||||||
|
if selling_price_list:
|
||||||
|
or_conditions.append(ip.price_list == selling_price_list)
|
||||||
|
|
||||||
|
res = (
|
||||||
|
qb.from_(ip)
|
||||||
|
.select(ip.item_code, ip.price_list, ip.price_list_rate)
|
||||||
|
.where(Criterion.all(and_conditions))
|
||||||
|
.where(Criterion.any(or_conditions))
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
for x in res:
|
||||||
|
price_list_map.update({(x.item_code, x.price_list): x.price_list_rate})
|
||||||
|
|
||||||
|
return price_list_map
|
||||||
|
|
||||||
|
|
||||||
def get_data(filters=None):
|
def get_data(filters=None):
|
||||||
data = []
|
data = []
|
||||||
customer_details = get_customer_details(filters)
|
customer_details = get_customer_details(filters)
|
||||||
@ -59,9 +95,17 @@ def get_data(filters=None):
|
|||||||
"Bin", fields=["item_code", "sum(actual_qty) AS available"], group_by="item_code"
|
"Bin", fields=["item_code", "sum(actual_qty) AS available"], group_by="item_code"
|
||||||
)
|
)
|
||||||
item_stock_map = {item.item_code: item.available for item in item_stock_map}
|
item_stock_map = {item.item_code: item.available for item in item_stock_map}
|
||||||
|
price_list_map = fetch_item_prices(
|
||||||
|
customer_details.customer,
|
||||||
|
customer_details.price_list,
|
||||||
|
customer_details.selling_price_list,
|
||||||
|
items,
|
||||||
|
)
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
price_list_rate = get_price_list_rate_for(customer_details, item.item_code) or 0.0
|
price_list_rate = price_list_map.get(
|
||||||
|
(item.item_code, customer_details.price_list or customer_details.selling_price_list), 0.0
|
||||||
|
)
|
||||||
available_stock = item_stock_map.get(item.item_code)
|
available_stock = item_stock_map.get(item.item_code)
|
||||||
|
|
||||||
data.append(
|
data.append(
|
||||||
|
@ -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(
|
||||||
{
|
{
|
||||||
|
@ -10,13 +10,13 @@
|
|||||||
"credit_in_account_currency": 40000.0,
|
"credit_in_account_currency": 40000.0,
|
||||||
"debit_in_account_currency": 0.0,
|
"debit_in_account_currency": 0.0,
|
||||||
"doctype": "Journal Entry Account",
|
"doctype": "Journal Entry Account",
|
||||||
"parentfield": "accounts",
|
"parentfield": "accounts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"credit_in_account_currency": 0.0,
|
"credit_in_account_currency": 0.0,
|
||||||
"debit_in_account_currency": 40000.0,
|
"debit_in_account_currency": 40000.0,
|
||||||
"doctype": "Journal Entry Account",
|
"doctype": "Journal Entry Account",
|
||||||
"parentfield": "accounts",
|
"parentfield": "accounts"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"user_remark": "test",
|
"user_remark": "test",
|
||||||
|
@ -274,7 +274,7 @@ class Company(NestedSet):
|
|||||||
"parent_warehouse": "{0} - {1}".format(_("All Warehouses"), self.abbr)
|
"parent_warehouse": "{0} - {1}".format(_("All Warehouses"), self.abbr)
|
||||||
if not wh_detail["is_group"]
|
if not wh_detail["is_group"]
|
||||||
else "",
|
else "",
|
||||||
"warehouse_type": wh_detail["warehouse_type"] if "warehouse_type" in wh_detail else None,
|
"warehouse_type": wh_detail.get("warehouse_type"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
warehouse.flags.ignore_permissions = True
|
warehouse.flags.ignore_permissions = True
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -11,23 +11,22 @@ frappe.ui.form.on("Territory", {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
});
|
refresh: function(frm) {
|
||||||
|
frm.trigger("set_root_readonly");
|
||||||
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
|
},
|
||||||
cur_frm.cscript.set_root_readonly(doc);
|
set_root_readonly: function(frm) {
|
||||||
}
|
|
||||||
|
|
||||||
cur_frm.cscript.set_root_readonly = function(doc) {
|
|
||||||
// read-only for root territory
|
// read-only for root territory
|
||||||
if(!doc.parent_territory && !doc.__islocal) {
|
if(!frm.doc.parent_territory && !frm.doc.__islocal) {
|
||||||
cur_frm.set_read_only();
|
frm.set_read_only();
|
||||||
cur_frm.set_intro(__("This is a root territory and cannot be edited."));
|
frm.set_intro(__("This is a root territory and cannot be edited."));
|
||||||
} else {
|
} else {
|
||||||
cur_frm.set_intro(null);
|
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{
|
||||||
|
@ -52,8 +52,7 @@
|
|||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Item",
|
"options": "Item",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"reqd": 1,
|
"reqd": 1
|
||||||
"search_index": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0.00",
|
"default": "0.00",
|
||||||
|
@ -301,7 +301,8 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Delivery Note",
|
"options": "Delivery Note",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
@ -1401,7 +1402,7 @@
|
|||||||
"idx": 146,
|
"idx": 146,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-09-04 14:15:28.363184",
|
"modified": "2023-12-18 17:19:39.368239",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Note",
|
"name": "Delivery Note",
|
||||||
|
@ -1294,7 +1294,3 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return doclist
|
return doclist
|
||||||
|
|
||||||
|
|
||||||
def on_doctype_update():
|
|
||||||
frappe.db.add_index("Delivery Note", ["customer", "is_return", "return_against"])
|
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
import click
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
UNUSED_INDEXES = [
|
||||||
|
("Delivery Note", ["customer", "is_return", "return_against"]),
|
||||||
|
("Sales Invoice", ["customer", "is_return", "return_against"]),
|
||||||
|
("Purchase Invoice", ["supplier", "is_return", "return_against"]),
|
||||||
|
("Purchase Receipt", ["supplier", "is_return", "return_against"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
for doctype, index_fields in UNUSED_INDEXES:
|
||||||
|
table = f"tab{doctype}"
|
||||||
|
index_name = frappe.db.get_index_name(index_fields)
|
||||||
|
drop_index_if_exists(table, index_name)
|
||||||
|
|
||||||
|
|
||||||
|
def drop_index_if_exists(table: str, index: str):
|
||||||
|
if not frappe.db.has_index(table, index):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
frappe.db.sql_ddl(f"ALTER TABLE `{table}` DROP INDEX `{index}`")
|
||||||
|
click.echo(f"✓ dropped {index} index from {table}")
|
||||||
|
except Exception:
|
||||||
|
frappe.log_error("Failed to drop index")
|
@ -174,6 +174,115 @@ class TestDeliveryNote(FrappeTestCase):
|
|||||||
for field, value in field_values.items():
|
for field, value in field_values.items():
|
||||||
self.assertEqual(cstr(serial_no.get(field)), value)
|
self.assertEqual(cstr(serial_no.get(field)), value)
|
||||||
|
|
||||||
|
def test_delivery_note_return_against_denormalized_serial_no(self):
|
||||||
|
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
|
frappe.flags.ignore_serial_batch_bundle_validation = True
|
||||||
|
sn_item = "Old Serial NO Item Return Test - 1"
|
||||||
|
make_item(
|
||||||
|
sn_item,
|
||||||
|
{
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"serial_no_series": "OSN-.####",
|
||||||
|
"is_stock_item": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.flags.ignore_serial_batch_bundle_validation = True
|
||||||
|
serial_nos = [
|
||||||
|
"OSN-1",
|
||||||
|
"OSN-2",
|
||||||
|
"OSN-3",
|
||||||
|
"OSN-4",
|
||||||
|
"OSN-5",
|
||||||
|
"OSN-6",
|
||||||
|
"OSN-7",
|
||||||
|
"OSN-8",
|
||||||
|
"OSN-9",
|
||||||
|
"OSN-10",
|
||||||
|
"OSN-11",
|
||||||
|
"OSN-12",
|
||||||
|
]
|
||||||
|
|
||||||
|
for sn in serial_nos:
|
||||||
|
if not frappe.db.exists("Serial No", sn):
|
||||||
|
sn_doc = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Serial No",
|
||||||
|
"item_code": sn_item,
|
||||||
|
"serial_no": sn,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
sn_doc.insert()
|
||||||
|
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
company = frappe.db.get_value("Warehouse", warehouse, "company")
|
||||||
|
se_doc = make_stock_entry(
|
||||||
|
item_code=sn_item,
|
||||||
|
company=company,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
qty=12,
|
||||||
|
basic_rate=100,
|
||||||
|
do_not_submit=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
se_doc.items[0].serial_no = "\n".join(serial_nos)
|
||||||
|
se_doc.submit()
|
||||||
|
|
||||||
|
self.assertEqual(sorted(get_serial_nos(se_doc.items[0].serial_no)), sorted(serial_nos))
|
||||||
|
|
||||||
|
dn = create_delivery_note(
|
||||||
|
item_code=sn_item,
|
||||||
|
qty=12,
|
||||||
|
rate=500,
|
||||||
|
warehouse=warehouse,
|
||||||
|
company=company,
|
||||||
|
expense_account="Cost of Goods Sold - _TC",
|
||||||
|
cost_center="Main - _TC",
|
||||||
|
do_not_submit=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
dn.items[0].serial_no = "\n".join(serial_nos)
|
||||||
|
dn.submit()
|
||||||
|
dn.reload()
|
||||||
|
|
||||||
|
self.assertTrue(dn.items[0].serial_no)
|
||||||
|
|
||||||
|
frappe.flags.ignore_serial_batch_bundle_validation = False
|
||||||
|
|
||||||
|
# return entry
|
||||||
|
dn1 = make_sales_return(dn.name)
|
||||||
|
|
||||||
|
dn1.items[0].qty = -2
|
||||||
|
|
||||||
|
bundle_doc = frappe.get_doc("Serial and Batch Bundle", dn1.items[0].serial_and_batch_bundle)
|
||||||
|
bundle_doc.set("entries", bundle_doc.entries[:2])
|
||||||
|
bundle_doc.save()
|
||||||
|
|
||||||
|
dn1.save()
|
||||||
|
dn1.submit()
|
||||||
|
|
||||||
|
returned_serial_nos1 = get_serial_nos_from_bundle(dn1.items[0].serial_and_batch_bundle)
|
||||||
|
for serial_no in returned_serial_nos1:
|
||||||
|
self.assertTrue(serial_no in serial_nos)
|
||||||
|
|
||||||
|
dn2 = make_sales_return(dn.name)
|
||||||
|
|
||||||
|
dn2.items[0].qty = -2
|
||||||
|
|
||||||
|
bundle_doc = frappe.get_doc("Serial and Batch Bundle", dn2.items[0].serial_and_batch_bundle)
|
||||||
|
bundle_doc.set("entries", bundle_doc.entries[:2])
|
||||||
|
bundle_doc.save()
|
||||||
|
|
||||||
|
dn2.save()
|
||||||
|
dn2.submit()
|
||||||
|
|
||||||
|
returned_serial_nos2 = get_serial_nos_from_bundle(dn2.items[0].serial_and_batch_bundle)
|
||||||
|
for serial_no in returned_serial_nos2:
|
||||||
|
self.assertTrue(serial_no in serial_nos)
|
||||||
|
self.assertFalse(serial_no in returned_serial_nos1)
|
||||||
|
|
||||||
def test_sales_return_for_non_bundled_items_partial(self):
|
def test_sales_return_for_non_bundled_items_partial(self):
|
||||||
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
|
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
|
||||||
|
|
||||||
@ -1266,6 +1375,109 @@ class TestDeliveryNote(FrappeTestCase):
|
|||||||
dn.reload()
|
dn.reload()
|
||||||
self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Delivered")
|
self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Delivered")
|
||||||
|
|
||||||
|
def test_sales_return_valuation_for_moving_average(self):
|
||||||
|
item_code = make_item(
|
||||||
|
"_Test Item Sales Return with MA", {"is_stock_item": 1, "valuation_method": "Moving Average"}
|
||||||
|
).name
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
qty=5,
|
||||||
|
basic_rate=100.0,
|
||||||
|
posting_date=add_days(nowdate(), -5),
|
||||||
|
)
|
||||||
|
dn = create_delivery_note(
|
||||||
|
item_code=item_code, qty=5, rate=500, posting_date=add_days(nowdate(), -4)
|
||||||
|
)
|
||||||
|
self.assertEqual(dn.items[0].incoming_rate, 100.0)
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
qty=5,
|
||||||
|
basic_rate=200.0,
|
||||||
|
posting_date=add_days(nowdate(), -3),
|
||||||
|
)
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
qty=5,
|
||||||
|
basic_rate=300.0,
|
||||||
|
posting_date=add_days(nowdate(), -2),
|
||||||
|
)
|
||||||
|
|
||||||
|
dn1 = create_delivery_note(
|
||||||
|
is_return=1,
|
||||||
|
item_code=item_code,
|
||||||
|
return_against=dn.name,
|
||||||
|
qty=-5,
|
||||||
|
rate=500,
|
||||||
|
company=dn.company,
|
||||||
|
expense_account="Cost of Goods Sold - _TC",
|
||||||
|
cost_center="Main - _TC",
|
||||||
|
do_not_submit=1,
|
||||||
|
posting_date=add_days(nowdate(), -1),
|
||||||
|
)
|
||||||
|
|
||||||
|
# (300 * 5) + (200 * 5) = 2500
|
||||||
|
# 2500 / 10 = 250
|
||||||
|
|
||||||
|
self.assertAlmostEqual(dn1.items[0].incoming_rate, 250.0)
|
||||||
|
|
||||||
|
def test_sales_return_valuation_for_moving_average_case2(self):
|
||||||
|
# Make DN return
|
||||||
|
# Make Bakcdated Purchase Receipt and check DN return valuation rate
|
||||||
|
# The rate should be recalculate based on the backdated purchase receipt
|
||||||
|
frappe.flags.print_debug_messages = False
|
||||||
|
item_code = make_item(
|
||||||
|
"_Test Item Sales Return with MA Case2",
|
||||||
|
{"is_stock_item": 1, "valuation_method": "Moving Average", "stock_uom": "Nos"},
|
||||||
|
).name
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
qty=5,
|
||||||
|
basic_rate=100.0,
|
||||||
|
posting_date=add_days(nowdate(), -5),
|
||||||
|
)
|
||||||
|
|
||||||
|
dn = create_delivery_note(
|
||||||
|
item_code=item_code,
|
||||||
|
warehouse="_Test Warehouse - _TC",
|
||||||
|
qty=5,
|
||||||
|
rate=500,
|
||||||
|
posting_date=add_days(nowdate(), -4),
|
||||||
|
)
|
||||||
|
|
||||||
|
returned_dn = create_delivery_note(
|
||||||
|
is_return=1,
|
||||||
|
item_code=item_code,
|
||||||
|
return_against=dn.name,
|
||||||
|
qty=-5,
|
||||||
|
rate=500,
|
||||||
|
company=dn.company,
|
||||||
|
warehouse="_Test Warehouse - _TC",
|
||||||
|
expense_account="Cost of Goods Sold - _TC",
|
||||||
|
cost_center="Main - _TC",
|
||||||
|
posting_date=add_days(nowdate(), -1),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertAlmostEqual(returned_dn.items[0].incoming_rate, 100.0)
|
||||||
|
|
||||||
|
# Make backdated purchase receipt
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
qty=5,
|
||||||
|
basic_rate=200.0,
|
||||||
|
posting_date=add_days(nowdate(), -3),
|
||||||
|
)
|
||||||
|
|
||||||
|
returned_dn.reload()
|
||||||
|
self.assertAlmostEqual(returned_dn.items[0].incoming_rate, 200.0)
|
||||||
|
|
||||||
|
|
||||||
def create_delivery_note(**args):
|
def create_delivery_note(**args):
|
||||||
dn = frappe.new_doc("Delivery Note")
|
dn = frappe.new_doc("Delivery Note")
|
||||||
|
@ -522,39 +522,25 @@ class TestItem(FrappeTestCase):
|
|||||||
self.assertEqual(factor, 1.0)
|
self.assertEqual(factor, 1.0)
|
||||||
|
|
||||||
def test_item_variant_by_manufacturer(self):
|
def test_item_variant_by_manufacturer(self):
|
||||||
fields = [{"field_name": "description"}, {"field_name": "variant_based_on"}]
|
template = make_item(
|
||||||
set_item_variant_settings(fields)
|
"_Test Item Variant By Manufacturer", {"has_variants": 1, "variant_based_on": "Manufacturer"}
|
||||||
|
).name
|
||||||
|
|
||||||
if frappe.db.exists("Item", "_Test Variant Mfg"):
|
for manufacturer in ["DFSS", "DASA", "ASAAS"]:
|
||||||
frappe.delete_doc("Item", "_Test Variant Mfg")
|
if not frappe.db.exists("Manufacturer", manufacturer):
|
||||||
if frappe.db.exists("Item", "_Test Variant Mfg-1"):
|
m_doc = frappe.new_doc("Manufacturer")
|
||||||
frappe.delete_doc("Item", "_Test Variant Mfg-1")
|
m_doc.short_name = manufacturer
|
||||||
if frappe.db.exists("Manufacturer", "MSG1"):
|
m_doc.insert()
|
||||||
frappe.delete_doc("Manufacturer", "MSG1")
|
|
||||||
|
|
||||||
template = frappe.get_doc(
|
self.assertFalse(frappe.db.exists("Item Manufacturer", {"manufacturer": "DFSS"}))
|
||||||
dict(
|
variant = get_variant(template, manufacturer="DFSS", manufacturer_part_no="DFSS-123")
|
||||||
doctype="Item",
|
|
||||||
item_code="_Test Variant Mfg",
|
item_manufacturer = frappe.db.exists(
|
||||||
has_variant=1,
|
"Item Manufacturer", {"manufacturer": "DFSS", "item_code": variant.name}
|
||||||
item_group="Products",
|
|
||||||
variant_based_on="Manufacturer",
|
|
||||||
)
|
)
|
||||||
).insert()
|
self.assertTrue(item_manufacturer)
|
||||||
|
|
||||||
manufacturer = frappe.get_doc(dict(doctype="Manufacturer", short_name="MSG1")).insert()
|
frappe.delete_doc("Item Manufacturer", item_manufacturer)
|
||||||
|
|
||||||
variant = get_variant(template.name, manufacturer=manufacturer.name)
|
|
||||||
self.assertEqual(variant.item_code, "_Test Variant Mfg-1")
|
|
||||||
self.assertEqual(variant.description, "_Test Variant Mfg")
|
|
||||||
self.assertEqual(variant.manufacturer, "MSG1")
|
|
||||||
variant.insert()
|
|
||||||
|
|
||||||
variant = get_variant(template.name, manufacturer=manufacturer.name, manufacturer_part_no="007")
|
|
||||||
self.assertEqual(variant.item_code, "_Test Variant Mfg-2")
|
|
||||||
self.assertEqual(variant.description, "_Test Variant Mfg")
|
|
||||||
self.assertEqual(variant.manufacturer, "MSG1")
|
|
||||||
self.assertEqual(variant.manufacturer_part_no, "007")
|
|
||||||
|
|
||||||
def test_stock_exists_against_template_item(self):
|
def test_stock_exists_against_template_item(self):
|
||||||
stock_item = frappe.get_all("Stock Ledger Entry", fields=["item_code"], limit=1)
|
stock_item = frappe.get_all("Stock Ledger Entry", fields=["item_code"], limit=1)
|
||||||
|
@ -169,7 +169,9 @@ class MaterialRequest(BuyingController):
|
|||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.update_requested_qty_in_production_plan()
|
self.update_requested_qty_in_production_plan()
|
||||||
self.update_requested_qty()
|
self.update_requested_qty()
|
||||||
if self.material_request_type == "Purchase":
|
if self.material_request_type == "Purchase" and frappe.db.exists(
|
||||||
|
"Budget", {"applicable_on_material_request": 1, "docstatus": 1}
|
||||||
|
):
|
||||||
self.validate_budget()
|
self.validate_budget()
|
||||||
|
|
||||||
def before_save(self):
|
def before_save(self):
|
||||||
|
@ -88,6 +88,20 @@ frappe.ui.form.on("Purchase Receipt", {
|
|||||||
}, __('Create'));
|
}, __('Create'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frm.doc.docstatus === 0) {
|
||||||
|
if (!frm.doc.is_return) {
|
||||||
|
frappe.db.get_single_value("Buying Settings", "maintain_same_rate").then((value) => {
|
||||||
|
if (value) {
|
||||||
|
frm.doc.items.forEach((item) => {
|
||||||
|
frm.fields_dict.items.grid.update_docfield_property(
|
||||||
|
"rate", "read_only", (item.purchase_order && item.purchase_order_item)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
frm.events.add_custom_buttons(frm);
|
frm.events.add_custom_buttons(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -289,7 +289,8 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Purchase Receipt",
|
"options": "Purchase Receipt",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_addresses",
|
"fieldname": "section_addresses",
|
||||||
@ -1251,7 +1252,7 @@
|
|||||||
"idx": 261,
|
"idx": 261,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-28 13:14:15.243474",
|
"modified": "2023-12-18 17:26:41.279663",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt",
|
"name": "Purchase Receipt",
|
||||||
|
@ -518,6 +518,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
debit=0.0,
|
debit=0.0,
|
||||||
credit=discrepancy_caused_by_exchange_rate_difference,
|
credit=discrepancy_caused_by_exchange_rate_difference,
|
||||||
remarks=remarks,
|
remarks=remarks,
|
||||||
|
against_type="Supplier",
|
||||||
against_account=self.supplier,
|
against_account=self.supplier,
|
||||||
debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
|
debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
|
||||||
account_currency=account_currency,
|
account_currency=account_currency,
|
||||||
@ -531,6 +532,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
debit=discrepancy_caused_by_exchange_rate_difference,
|
debit=discrepancy_caused_by_exchange_rate_difference,
|
||||||
credit=0.0,
|
credit=0.0,
|
||||||
remarks=remarks,
|
remarks=remarks,
|
||||||
|
against_type="Supplier",
|
||||||
against_account=self.supplier,
|
against_account=self.supplier,
|
||||||
debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
|
debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
|
||||||
account_currency=account_currency,
|
account_currency=account_currency,
|
||||||
@ -796,7 +798,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
# Backward compatibility:
|
# Backward compatibility:
|
||||||
# and charges added via Landed Cost Voucher,
|
# and charges added via Landed Cost Voucher,
|
||||||
# post valuation related charges on "Stock Received But Not Billed"
|
# post valuation related charges on "Stock Received But Not Billed"
|
||||||
against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0])
|
against_accounts = [d.account for d in gl_entries if flt(d.debit) > 0]
|
||||||
total_valuation_amount = sum(valuation_tax.values())
|
total_valuation_amount = sum(valuation_tax.values())
|
||||||
amount_including_divisional_loss = negative_expense_to_be_booked
|
amount_including_divisional_loss = negative_expense_to_be_booked
|
||||||
stock_rbnb = (
|
stock_rbnb = (
|
||||||
@ -828,14 +830,15 @@ class PurchaseReceipt(BuyingController):
|
|||||||
)
|
)
|
||||||
amount_including_divisional_loss -= applicable_amount
|
amount_including_divisional_loss -= applicable_amount
|
||||||
|
|
||||||
|
for against in against_accounts:
|
||||||
self.add_gl_entry(
|
self.add_gl_entry(
|
||||||
gl_entries=gl_entries,
|
gl_entries=gl_entries,
|
||||||
account=account,
|
account=account,
|
||||||
cost_center=tax.cost_center,
|
cost_center=tax.cost_center,
|
||||||
debit=0.0,
|
debit=0.0,
|
||||||
credit=applicable_amount,
|
credit=flt(applicable_amount) / len(against_accounts),
|
||||||
remarks=self.remarks or _("Accounting Entry for Stock"),
|
remarks=self.remarks or _("Accounting Entry for Stock"),
|
||||||
against_account=against_account,
|
against_account=against,
|
||||||
item=tax,
|
item=tax,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1357,10 +1360,6 @@ def get_item_account_wise_additional_cost(purchase_document):
|
|||||||
return item_account_wise_cost
|
return item_account_wise_cost
|
||||||
|
|
||||||
|
|
||||||
def on_doctype_update():
|
|
||||||
frappe.db.add_index("Purchase Receipt", ["supplier", "is_return", "return_against"])
|
|
||||||
|
|
||||||
|
|
||||||
@erpnext.allow_regional
|
@erpnext.allow_regional
|
||||||
def update_regional_gl_entries(gl_list, doc):
|
def update_regional_gl_entries(gl_list, doc):
|
||||||
return
|
return
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import add_days, cint, cstr, flt, nowtime, today
|
from frappe.utils import add_days, cint, cstr, flt, nowtime, today
|
||||||
|
from pypika import Order
|
||||||
from pypika import functions as fn
|
from pypika import functions as fn
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
@ -990,7 +991,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
|
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
|
||||||
sl_entries = get_sl_entries("Purchase Receipt", pr.name)
|
sl_entries = get_sl_entries("Purchase Receipt", pr.name)
|
||||||
|
|
||||||
self.assertFalse(gl_entries)
|
self.assertEqual(len(gl_entries), 2)
|
||||||
|
|
||||||
expected_sle = {"Work In Progress - TCP1": -5, "Stores - TCP1": 5}
|
expected_sle = {"Work In Progress - TCP1": -5, "Stores - TCP1": 5}
|
||||||
|
|
||||||
@ -2235,13 +2236,13 @@ def get_sl_entries(voucher_type, voucher_no):
|
|||||||
|
|
||||||
|
|
||||||
def get_gl_entries(voucher_type, voucher_no):
|
def get_gl_entries(voucher_type, voucher_no):
|
||||||
return frappe.db.sql(
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
"""select account, debit, credit, cost_center, is_cancelled
|
return (
|
||||||
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
|
frappe.qb.from_(gle)
|
||||||
order by account desc""",
|
.select(gle.account, gle.debit, gle.credit, gle.cost_center, gle.is_cancelled)
|
||||||
(voucher_type, voucher_no),
|
.where((gle.voucher_type == voucher_type) & (gle.voucher_no == voucher_no))
|
||||||
as_dict=1,
|
.orderby(gle.account, gle.debit, order=Order.desc)
|
||||||
)
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
|
||||||
def get_taxes(**args):
|
def get_taxes(**args):
|
||||||
|
@ -359,7 +359,6 @@
|
|||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "currency",
|
"options": "currency",
|
||||||
"print_width": "100px",
|
"print_width": "100px",
|
||||||
"read_only_depends_on": "eval: (!parent.is_return && doc.purchase_order && doc.purchase_order_item)",
|
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1104,7 +1103,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-30 16:12:02.364608",
|
"modified": "2023-12-25 22:32:09.801965",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt Item",
|
"name": "Purchase Receipt Item",
|
||||||
|
@ -23,7 +23,11 @@ from frappe.utils import (
|
|||||||
)
|
)
|
||||||
from frappe.utils.csvutils import build_csv_response
|
from frappe.utils.csvutils import build_csv_response
|
||||||
|
|
||||||
from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation
|
from erpnext.stock.serial_batch_bundle import (
|
||||||
|
BatchNoValuation,
|
||||||
|
SerialNoValuation,
|
||||||
|
get_batches_from_bundle,
|
||||||
|
)
|
||||||
from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle
|
from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle
|
||||||
|
|
||||||
|
|
||||||
@ -81,6 +85,7 @@ class SerialandBatchBundle(Document):
|
|||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.set_batch_no()
|
||||||
self.validate_serial_and_batch_no()
|
self.validate_serial_and_batch_no()
|
||||||
self.validate_duplicate_serial_and_batch_no()
|
self.validate_duplicate_serial_and_batch_no()
|
||||||
self.validate_voucher_no()
|
self.validate_voucher_no()
|
||||||
@ -95,6 +100,26 @@ class SerialandBatchBundle(Document):
|
|||||||
self.set_incoming_rate()
|
self.set_incoming_rate()
|
||||||
self.calculate_qty_and_amount()
|
self.calculate_qty_and_amount()
|
||||||
|
|
||||||
|
def set_batch_no(self):
|
||||||
|
if self.has_serial_no and self.has_batch_no:
|
||||||
|
serial_nos = [d.serial_no for d in self.entries if d.serial_no]
|
||||||
|
has_no_batch = any(not d.batch_no for d in self.entries)
|
||||||
|
if not has_no_batch:
|
||||||
|
return
|
||||||
|
|
||||||
|
serial_no_batch = frappe._dict(
|
||||||
|
frappe.get_all(
|
||||||
|
"Serial No",
|
||||||
|
filters={"name": ("in", serial_nos)},
|
||||||
|
fields=["name", "batch_no"],
|
||||||
|
as_list=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in self.entries:
|
||||||
|
if not row.batch_no:
|
||||||
|
row.batch_no = serial_no_batch.get(row.serial_no)
|
||||||
|
|
||||||
def validate_serial_nos_inventory(self):
|
def validate_serial_nos_inventory(self):
|
||||||
if not (self.has_serial_no and self.type_of_transaction == "Outward"):
|
if not (self.has_serial_no and self.type_of_transaction == "Outward"):
|
||||||
return
|
return
|
||||||
@ -123,6 +148,11 @@ class SerialandBatchBundle(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def validate_serial_nos_duplicate(self):
|
def validate_serial_nos_duplicate(self):
|
||||||
|
# Don't inward same serial number multiple times
|
||||||
|
|
||||||
|
if not self.warehouse:
|
||||||
|
return
|
||||||
|
|
||||||
if self.voucher_type in ["Stock Reconciliation", "Stock Entry"] and self.docstatus != 1:
|
if self.voucher_type in ["Stock Reconciliation", "Stock Entry"] and self.docstatus != 1:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -146,7 +176,6 @@ class SerialandBatchBundle(Document):
|
|||||||
kwargs["voucher_no"] = self.voucher_no
|
kwargs["voucher_no"] = self.voucher_no
|
||||||
|
|
||||||
available_serial_nos = get_available_serial_nos(kwargs)
|
available_serial_nos = get_available_serial_nos(kwargs)
|
||||||
|
|
||||||
for data in available_serial_nos:
|
for data in available_serial_nos:
|
||||||
if data.serial_no in serial_nos:
|
if data.serial_no in serial_nos:
|
||||||
self.throw_error_message(
|
self.throw_error_message(
|
||||||
@ -327,6 +356,19 @@ class SerialandBatchBundle(Document):
|
|||||||
):
|
):
|
||||||
values_to_set["posting_time"] = parent.posting_time
|
values_to_set["posting_time"] = parent.posting_time
|
||||||
|
|
||||||
|
if parent.doctype in [
|
||||||
|
"Delivery Note",
|
||||||
|
"Purchase Receipt",
|
||||||
|
"Purchase Invoice",
|
||||||
|
"Sales Invoice",
|
||||||
|
] and parent.get("is_return"):
|
||||||
|
return_ref_field = frappe.scrub(parent.doctype) + "_item"
|
||||||
|
if parent.doctype == "Delivery Note":
|
||||||
|
return_ref_field = "dn_detail"
|
||||||
|
|
||||||
|
if row.get(return_ref_field):
|
||||||
|
values_to_set["returned_against"] = row.get(return_ref_field)
|
||||||
|
|
||||||
if values_to_set:
|
if values_to_set:
|
||||||
self.db_set(values_to_set)
|
self.db_set(values_to_set)
|
||||||
|
|
||||||
@ -438,7 +480,7 @@ class SerialandBatchBundle(Document):
|
|||||||
qty_field = "qty"
|
qty_field = "qty"
|
||||||
|
|
||||||
precision = row.precision
|
precision = row.precision
|
||||||
if self.voucher_type in ["Subcontracting Receipt"]:
|
if row.get("doctype") in ["Subcontracting Receipt Supplied Item"]:
|
||||||
qty_field = "consumed_qty"
|
qty_field = "consumed_qty"
|
||||||
|
|
||||||
if abs(abs(flt(self.total_qty, precision)) - abs(flt(row.get(qty_field), precision))) > 0.01:
|
if abs(abs(flt(self.total_qty, precision)) - abs(flt(row.get(qty_field), precision))) > 0.01:
|
||||||
@ -509,7 +551,6 @@ class SerialandBatchBundle(Document):
|
|||||||
batch_nos = []
|
batch_nos = []
|
||||||
|
|
||||||
serial_batches = {}
|
serial_batches = {}
|
||||||
|
|
||||||
for row in self.entries:
|
for row in self.entries:
|
||||||
if self.has_serial_no and not row.serial_no:
|
if self.has_serial_no and not row.serial_no:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
@ -590,6 +631,67 @@ class SerialandBatchBundle(Document):
|
|||||||
f"Batch Nos {bold(incorrect_batch_nos)} does not belong to Item {bold(self.item_code)}"
|
f"Batch Nos {bold(incorrect_batch_nos)} does not belong to Item {bold(self.item_code)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_serial_and_batch_no_for_returned(self):
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
|
if not self.returned_against:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.voucher_type not in [
|
||||||
|
"Purchase Receipt",
|
||||||
|
"Purchase Invoice",
|
||||||
|
"Sales Invoice",
|
||||||
|
"Delivery Note",
|
||||||
|
]:
|
||||||
|
return
|
||||||
|
|
||||||
|
data = self.get_orignal_document_data()
|
||||||
|
if not data:
|
||||||
|
return
|
||||||
|
|
||||||
|
serial_nos, batches = [], []
|
||||||
|
current_serial_nos = [d.serial_no for d in self.entries if d.serial_no]
|
||||||
|
current_batches = [d.batch_no for d in self.entries if d.batch_no]
|
||||||
|
|
||||||
|
for d in data:
|
||||||
|
if self.has_serial_no:
|
||||||
|
if d.serial_and_batch_bundle:
|
||||||
|
serial_nos = get_serial_nos_from_bundle(d.serial_and_batch_bundle)
|
||||||
|
else:
|
||||||
|
serial_nos = get_serial_nos(d.serial_no)
|
||||||
|
|
||||||
|
elif self.has_batch_no:
|
||||||
|
if d.serial_and_batch_bundle:
|
||||||
|
batches = get_batches_from_bundle(d.serial_and_batch_bundle)
|
||||||
|
else:
|
||||||
|
batches = frappe._dict({d.batch_no: d.stock_qty})
|
||||||
|
|
||||||
|
if batches:
|
||||||
|
batches = [d for d in batches if batches[d] > 0]
|
||||||
|
|
||||||
|
if serial_nos:
|
||||||
|
if not set(current_serial_nos).issubset(set(serial_nos)):
|
||||||
|
self.throw_error_message(
|
||||||
|
f"Serial Nos {bold(', '.join(serial_nos))} are not part of the original document."
|
||||||
|
)
|
||||||
|
|
||||||
|
if batches:
|
||||||
|
if not set(current_batches).issubset(set(batches)):
|
||||||
|
self.throw_error_message(
|
||||||
|
f"Batch Nos {bold(', '.join(batches))} are not part of the original document."
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_orignal_document_data(self):
|
||||||
|
fields = ["serial_and_batch_bundle", "stock_qty"]
|
||||||
|
if self.has_serial_no:
|
||||||
|
fields.append("serial_no")
|
||||||
|
|
||||||
|
elif self.has_batch_no:
|
||||||
|
fields.append("batch_no")
|
||||||
|
|
||||||
|
child_doc = self.voucher_type + " Item"
|
||||||
|
return frappe.get_all(child_doc, fields=fields, filters={"name": self.returned_against})
|
||||||
|
|
||||||
def validate_duplicate_serial_and_batch_no(self):
|
def validate_duplicate_serial_and_batch_no(self):
|
||||||
serial_nos = []
|
serial_nos = []
|
||||||
batch_nos = []
|
batch_nos = []
|
||||||
@ -688,9 +790,29 @@ class SerialandBatchBundle(Document):
|
|||||||
for batch in batches:
|
for batch in batches:
|
||||||
frappe.db.set_value("Batch", batch.name, {"reference_name": None, "reference_doctype": None})
|
frappe.db.set_value("Batch", batch.name, {"reference_name": None, "reference_doctype": None})
|
||||||
|
|
||||||
|
def before_submit(self):
|
||||||
|
self.validate_serial_and_batch_no_for_returned()
|
||||||
|
self.set_purchase_document_no()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.validate_serial_nos_inventory()
|
self.validate_serial_nos_inventory()
|
||||||
|
|
||||||
|
def set_purchase_document_no(self):
|
||||||
|
if not self.has_serial_no:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.total_qty > 0:
|
||||||
|
serial_nos = [d.serial_no for d in self.entries if d.serial_no]
|
||||||
|
sn_table = frappe.qb.DocType("Serial No")
|
||||||
|
(
|
||||||
|
frappe.qb.update(sn_table)
|
||||||
|
.set(
|
||||||
|
sn_table.purchase_document_no,
|
||||||
|
self.voucher_no if not sn_table.purchase_document_no else self.voucher_no,
|
||||||
|
)
|
||||||
|
.where(sn_table.name.isin(serial_nos))
|
||||||
|
).run()
|
||||||
|
|
||||||
def validate_serial_and_batch_inventory(self):
|
def validate_serial_and_batch_inventory(self):
|
||||||
self.check_future_entries_exists()
|
self.check_future_entries_exists()
|
||||||
self.validate_batch_inventory()
|
self.validate_batch_inventory()
|
||||||
@ -1063,7 +1185,7 @@ def create_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse=Non
|
|||||||
doc.append(
|
doc.append(
|
||||||
"entries",
|
"entries",
|
||||||
{
|
{
|
||||||
"qty": (row.qty or 1.0) * (1 if type_of_transaction == "Inward" else -1),
|
"qty": (flt(row.qty) or 1.0) * (1 if type_of_transaction == "Inward" else -1),
|
||||||
"warehouse": warehouse,
|
"warehouse": warehouse,
|
||||||
"batch_no": row.batch_no,
|
"batch_no": row.batch_no,
|
||||||
"serial_no": row.serial_no,
|
"serial_no": row.serial_no,
|
||||||
@ -1091,7 +1213,7 @@ def update_serial_batch_no_ledgers(entries, child_row, parent_doc, warehouse=Non
|
|||||||
doc.append(
|
doc.append(
|
||||||
"entries",
|
"entries",
|
||||||
{
|
{
|
||||||
"qty": (d.get("qty") or 1.0) * (1 if doc.type_of_transaction == "Inward" else -1),
|
"qty": (flt(d.get("qty")) or 1.0) * (1 if doc.type_of_transaction == "Inward" else -1),
|
||||||
"warehouse": warehouse or d.get("warehouse"),
|
"warehouse": warehouse or d.get("warehouse"),
|
||||||
"batch_no": d.get("batch_no"),
|
"batch_no": d.get("batch_no"),
|
||||||
"serial_no": d.get("serial_no"),
|
"serial_no": d.get("serial_no"),
|
||||||
|
@ -27,8 +27,6 @@
|
|||||||
"column_break_24",
|
"column_break_24",
|
||||||
"location",
|
"location",
|
||||||
"employee",
|
"employee",
|
||||||
"delivery_details",
|
|
||||||
"delivery_document_type",
|
|
||||||
"warranty_amc_details",
|
"warranty_amc_details",
|
||||||
"column_break6",
|
"column_break6",
|
||||||
"warranty_expiry_date",
|
"warranty_expiry_date",
|
||||||
@ -39,7 +37,8 @@
|
|||||||
"more_info",
|
"more_info",
|
||||||
"company",
|
"company",
|
||||||
"column_break_2cmm",
|
"column_break_2cmm",
|
||||||
"work_order"
|
"work_order",
|
||||||
|
"purchase_document_no"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -153,20 +152,6 @@
|
|||||||
"options": "Employee",
|
"options": "Employee",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "delivery_details",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Delivery Details",
|
|
||||||
"oldfieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "delivery_document_type",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Delivery Document Type",
|
|
||||||
"no_copy": 1,
|
|
||||||
"options": "DocType",
|
|
||||||
"read_only": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "warranty_amc_details",
|
"fieldname": "warranty_amc_details",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
@ -275,12 +260,19 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_2cmm",
|
"fieldname": "column_break_2cmm",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "purchase_document_no",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Creation Document No",
|
||||||
|
"no_copy": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-barcode",
|
"icon": "fa fa-barcode",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-28 15:37:59.489945",
|
"modified": "2023-12-17 10:52:55.767839",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Serial No",
|
"name": "Serial No",
|
||||||
|
@ -41,7 +41,6 @@ class SerialNo(StockController):
|
|||||||
batch_no: DF.Link | None
|
batch_no: DF.Link | None
|
||||||
brand: DF.Link | None
|
brand: DF.Link | None
|
||||||
company: DF.Link
|
company: DF.Link
|
||||||
delivery_document_type: DF.Link | None
|
|
||||||
description: DF.Text | None
|
description: DF.Text | None
|
||||||
employee: DF.Link | None
|
employee: DF.Link | None
|
||||||
item_code: DF.Link
|
item_code: DF.Link
|
||||||
@ -51,6 +50,7 @@ class SerialNo(StockController):
|
|||||||
maintenance_status: DF.Literal[
|
maintenance_status: DF.Literal[
|
||||||
"", "Under Warranty", "Out of Warranty", "Under AMC", "Out of AMC"
|
"", "Under Warranty", "Out of Warranty", "Under AMC", "Out of AMC"
|
||||||
]
|
]
|
||||||
|
purchase_document_no: DF.Data | None
|
||||||
purchase_rate: DF.Float
|
purchase_rate: DF.Float
|
||||||
serial_no: DF.Data
|
serial_no: DF.Data
|
||||||
status: DF.Literal["", "Active", "Inactive", "Delivered", "Expired"]
|
status: DF.Literal["", "Active", "Inactive", "Delivered", "Expired"]
|
||||||
@ -231,26 +231,6 @@ def auto_fetch_serial_number(
|
|||||||
return sorted([d.get("name") for d in serial_numbers])
|
return sorted([d.get("name") for d in serial_numbers])
|
||||||
|
|
||||||
|
|
||||||
def get_delivered_serial_nos(serial_nos):
|
|
||||||
"""
|
|
||||||
Returns serial numbers that delivered from the list of serial numbers
|
|
||||||
"""
|
|
||||||
from frappe.query_builder.functions import Coalesce
|
|
||||||
|
|
||||||
SerialNo = frappe.qb.DocType("Serial No")
|
|
||||||
serial_nos = get_serial_nos(serial_nos)
|
|
||||||
query = (
|
|
||||||
frappe.qb.select(SerialNo.name)
|
|
||||||
.from_(SerialNo)
|
|
||||||
.where((SerialNo.name.isin(serial_nos)) & (Coalesce(SerialNo.delivery_document_type, "") != ""))
|
|
||||||
)
|
|
||||||
|
|
||||||
result = query.run()
|
|
||||||
if result and len(result) > 0:
|
|
||||||
delivered_serial_nos = [row[0] for row in result]
|
|
||||||
return delivered_serial_nos
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_pos_reserved_serial_nos(filters):
|
def get_pos_reserved_serial_nos(filters):
|
||||||
if isinstance(filters, str):
|
if isinstance(filters, str):
|
||||||
|
@ -512,7 +512,12 @@ frappe.ui.form.on('Stock Entry', {
|
|||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if (!r.exc) {
|
if (!r.exc) {
|
||||||
["actual_qty", "basic_rate"].forEach((field) => {
|
let fields = ["actual_qty", "basic_rate"];
|
||||||
|
if (frm.doc.purpose == "Material Receipt") {
|
||||||
|
fields = ["actual_qty"];
|
||||||
|
}
|
||||||
|
|
||||||
|
fields.forEach((field) => {
|
||||||
frappe.model.set_value(cdt, cdn, field, (r.message[field] || 0.0));
|
frappe.model.set_value(cdt, cdn, field, (r.message[field] || 0.0));
|
||||||
});
|
});
|
||||||
frm.events.calculate_basic_amount(frm, child);
|
frm.events.calculate_basic_amount(frm, child);
|
||||||
|
@ -1463,7 +1463,9 @@ class StockEntry(StockController):
|
|||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
"account": account,
|
"account": account,
|
||||||
|
"against_type": "Account",
|
||||||
"against": d.expense_account,
|
"against": d.expense_account,
|
||||||
|
"against_link": d.expense_account,
|
||||||
"cost_center": d.cost_center,
|
"cost_center": d.cost_center,
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||||
"credit_in_account_currency": flt(amount["amount"]),
|
"credit_in_account_currency": flt(amount["amount"]),
|
||||||
@ -1477,7 +1479,9 @@ class StockEntry(StockController):
|
|||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
{
|
||||||
"account": d.expense_account,
|
"account": d.expense_account,
|
||||||
|
"against_type": "Account",
|
||||||
"against": account,
|
"against": account,
|
||||||
|
"against_link": account,
|
||||||
"cost_center": d.cost_center,
|
"cost_center": d.cost_center,
|
||||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||||
"credit": -1
|
"credit": -1
|
||||||
|
@ -504,7 +504,14 @@ class TestStockEntry(FrappeTestCase):
|
|||||||
self.check_gl_entries(
|
self.check_gl_entries(
|
||||||
"Stock Entry",
|
"Stock Entry",
|
||||||
repack.name,
|
repack.name,
|
||||||
sorted([[stock_in_hand_account, 1200, 0.0], ["Cost of Goods Sold - TCP1", 0.0, 1200.0]]),
|
sorted(
|
||||||
|
[
|
||||||
|
["Cost of Goods Sold - TCP1", 0.0, 1200.0],
|
||||||
|
["Stock Adjustment - TCP1", 0.0, 1200.0],
|
||||||
|
["Stock Adjustment - TCP1", 1200.0, 0.0],
|
||||||
|
[stock_in_hand_account, 1200.0, 0.0],
|
||||||
|
]
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def check_stock_ledger_entries(self, voucher_type, voucher_no, expected_sle):
|
def check_stock_ledger_entries(self, voucher_type, voucher_no, expected_sle):
|
||||||
|
@ -181,6 +181,9 @@ class StockLedgerEntry(Document):
|
|||||||
frappe.throw(_("Actual Qty is mandatory"))
|
frappe.throw(_("Actual Qty is mandatory"))
|
||||||
|
|
||||||
def validate_serial_batch_no_bundle(self):
|
def validate_serial_batch_no_bundle(self):
|
||||||
|
if self.is_cancelled == 1:
|
||||||
|
return
|
||||||
|
|
||||||
item_detail = frappe.get_cached_value(
|
item_detail = frappe.get_cached_value(
|
||||||
"Item",
|
"Item",
|
||||||
self.item_code,
|
self.item_code,
|
||||||
|
@ -171,7 +171,7 @@ class StockReconciliation(StockController):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if item_details.has_batch_no:
|
elif item_details.has_batch_no:
|
||||||
batch_nos_details = get_available_batches(
|
batch_nos_details = get_available_batches(
|
||||||
frappe._dict(
|
frappe._dict(
|
||||||
{
|
{
|
||||||
@ -228,6 +228,9 @@ class StockReconciliation(StockController):
|
|||||||
|
|
||||||
def set_new_serial_and_batch_bundle(self):
|
def set_new_serial_and_batch_bundle(self):
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
|
if not item.qty:
|
||||||
|
continue
|
||||||
|
|
||||||
if item.current_serial_and_batch_bundle and not item.serial_and_batch_bundle:
|
if item.current_serial_and_batch_bundle and not item.serial_and_batch_bundle:
|
||||||
current_doc = frappe.get_doc("Serial and Batch Bundle", item.current_serial_and_batch_bundle)
|
current_doc = frappe.get_doc("Serial and Batch Bundle", item.current_serial_and_batch_bundle)
|
||||||
|
|
||||||
|
@ -865,6 +865,66 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
|||||||
sr1.load_from_db()
|
sr1.load_from_db()
|
||||||
self.assertEqual(sr1.difference_amount, 10000)
|
self.assertEqual(sr1.difference_amount, 10000)
|
||||||
|
|
||||||
|
def test_make_stock_zero_for_serial_batch_item(self):
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
|
||||||
|
serial_item = self.make_item(
|
||||||
|
properties={"is_stock_item": 1, "has_serial_no": 1, "serial_no_series": "DJJ.####"}
|
||||||
|
).name
|
||||||
|
batch_item = self.make_item(
|
||||||
|
properties={
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"batch_number_series": "BDJJ.####",
|
||||||
|
"create_new_batch": 1,
|
||||||
|
}
|
||||||
|
).name
|
||||||
|
|
||||||
|
serial_batch_item = self.make_item(
|
||||||
|
properties={
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"batch_number_series": "ADJJ.####",
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"serial_no_series": "SN-ADJJ.####",
|
||||||
|
}
|
||||||
|
).name
|
||||||
|
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
|
||||||
|
for item_code in [serial_item, batch_item, serial_batch_item]:
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=item_code,
|
||||||
|
target=warehouse,
|
||||||
|
qty=10,
|
||||||
|
basic_rate=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
_reco = create_stock_reconciliation(
|
||||||
|
item_code=item_code,
|
||||||
|
warehouse=warehouse,
|
||||||
|
qty=0.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
serial_batch_bundle = frappe.get_all(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0, "voucher_no": _reco.name},
|
||||||
|
"serial_and_batch_bundle",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(serial_batch_bundle), 1)
|
||||||
|
|
||||||
|
_reco.cancel()
|
||||||
|
|
||||||
|
serial_batch_bundle = frappe.get_all(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0, "voucher_no": _reco.name},
|
||||||
|
"serial_and_batch_bundle",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(serial_batch_bundle), 0)
|
||||||
|
|
||||||
|
|
||||||
def create_batch_item_with_batch(item_name, batch_id):
|
def create_batch_item_with_batch(item_name, batch_id):
|
||||||
batch_item_doc = create_item(item_name, is_stock_item=1)
|
batch_item_doc = create_item(item_name, is_stock_item=1)
|
||||||
|
@ -9,7 +9,7 @@ from frappe.model.document import Document
|
|||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import cint, flt
|
from frappe.utils import cint, flt
|
||||||
|
|
||||||
from erpnext.stock.utils import get_or_make_bin
|
from erpnext.stock.utils import get_or_make_bin, get_stock_balance
|
||||||
|
|
||||||
|
|
||||||
class StockReservationEntry(Document):
|
class StockReservationEntry(Document):
|
||||||
@ -151,7 +151,7 @@ class StockReservationEntry(Document):
|
|||||||
"""Validates `Reserved Qty` when `Reservation Based On` is `Qty`."""
|
"""Validates `Reserved Qty` when `Reservation Based On` is `Qty`."""
|
||||||
|
|
||||||
if self.reservation_based_on == "Qty":
|
if self.reservation_based_on == "Qty":
|
||||||
self.validate_with_max_reserved_qty(self.reserved_qty)
|
self.validate_with_allowed_qty(self.reserved_qty)
|
||||||
|
|
||||||
def auto_reserve_serial_and_batch(self, based_on: str = None) -> None:
|
def auto_reserve_serial_and_batch(self, based_on: str = None) -> None:
|
||||||
"""Auto pick Serial and Batch Nos to reserve when `Reservation Based On` is `Serial and Batch`."""
|
"""Auto pick Serial and Batch Nos to reserve when `Reservation Based On` is `Serial and Batch`."""
|
||||||
@ -324,7 +324,7 @@ class StockReservationEntry(Document):
|
|||||||
frappe.throw(msg)
|
frappe.throw(msg)
|
||||||
|
|
||||||
# Should be called after validating Serial and Batch Nos.
|
# Should be called after validating Serial and Batch Nos.
|
||||||
self.validate_with_max_reserved_qty(qty_to_be_reserved)
|
self.validate_with_allowed_qty(qty_to_be_reserved)
|
||||||
self.db_set("reserved_qty", qty_to_be_reserved)
|
self.db_set("reserved_qty", qty_to_be_reserved)
|
||||||
|
|
||||||
def update_reserved_qty_in_voucher(
|
def update_reserved_qty_in_voucher(
|
||||||
@ -429,7 +429,7 @@ class StockReservationEntry(Document):
|
|||||||
msg = _("Stock Reservation Entry cannot be updated as it has been delivered.")
|
msg = _("Stock Reservation Entry cannot be updated as it has been delivered.")
|
||||||
frappe.throw(msg)
|
frappe.throw(msg)
|
||||||
|
|
||||||
def validate_with_max_reserved_qty(self, qty_to_be_reserved: float) -> None:
|
def validate_with_allowed_qty(self, qty_to_be_reserved: float) -> None:
|
||||||
"""Validates `Reserved Qty` with `Max Reserved Qty`."""
|
"""Validates `Reserved Qty` with `Max Reserved Qty`."""
|
||||||
|
|
||||||
self.db_set(
|
self.db_set(
|
||||||
@ -448,12 +448,12 @@ class StockReservationEntry(Document):
|
|||||||
)
|
)
|
||||||
voucher_delivered_qty = flt(delivered_qty) * flt(conversion_factor)
|
voucher_delivered_qty = flt(delivered_qty) * flt(conversion_factor)
|
||||||
|
|
||||||
max_reserved_qty = min(
|
allowed_qty = min(
|
||||||
self.available_qty, (self.voucher_qty - voucher_delivered_qty - total_reserved_qty)
|
self.available_qty, (self.voucher_qty - voucher_delivered_qty - total_reserved_qty)
|
||||||
)
|
)
|
||||||
|
|
||||||
if max_reserved_qty <= 0 and self.voucher_type == "Sales Order":
|
if self.get("_action") != "submit" and self.voucher_type == "Sales Order" and allowed_qty <= 0:
|
||||||
msg = _("Item {0} is already delivered for Sales Order {1}.").format(
|
msg = _("Item {0} is already reserved/delivered against Sales Order {1}.").format(
|
||||||
frappe.bold(self.item_code), frappe.bold(self.voucher_no)
|
frappe.bold(self.item_code), frappe.bold(self.voucher_no)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -463,19 +463,33 @@ class StockReservationEntry(Document):
|
|||||||
else:
|
else:
|
||||||
frappe.throw(msg)
|
frappe.throw(msg)
|
||||||
|
|
||||||
if qty_to_be_reserved > max_reserved_qty:
|
if qty_to_be_reserved > allowed_qty:
|
||||||
|
actual_qty = get_stock_balance(self.item_code, self.warehouse)
|
||||||
msg = """
|
msg = """
|
||||||
Cannot reserve more than Max Reserved Qty {0} {1}.<br /><br />
|
Cannot reserve more than Allowed Qty {0} {1} for Item {2} against {3} {4}.<br /><br />
|
||||||
The <b>Max Reserved Qty</b> is calculated as follows:<br />
|
The <b>Allowed Qty</b> is calculated as follows:<br />
|
||||||
<ul>
|
<ul>
|
||||||
<li><b>Available Qty To Reserve</b> = (Actual Stock Qty - Reserved Stock Qty)</li>
|
<li>Actual Qty [Available Qty at Warehouse] = {5}</li>
|
||||||
<li><b>Voucher Qty</b> = Voucher Item Qty</li>
|
<li>Reserved Stock [Ignore current SRE] = {6}</li>
|
||||||
<li><b>Delivered Qty</b> = Qty delivered against the Voucher Item</li>
|
<li>Available Qty To Reserve [Actual Qty - Reserved Stock] = {7}</li>
|
||||||
<li><b>Total Reserved Qty</b> = Qty reserved against the Voucher Item</li>
|
<li>Voucher Qty [Voucher Item Qty] = {8}</li>
|
||||||
<li><b>Max Reserved Qty</b> = Minimum of (Available Qty To Reserve, (Voucher Qty - Delivered Qty - Total Reserved Qty))</li>
|
<li>Delivered Qty [Qty delivered against the Voucher Item] = {9}</li>
|
||||||
|
<li>Total Reserved Qty [Qty reserved against the Voucher Item] = {10}</li>
|
||||||
|
<li>Allowed Qty [Minimum of (Available Qty To Reserve, (Voucher Qty - Delivered Qty - Total Reserved Qty))] = {11}</li>
|
||||||
</ul>
|
</ul>
|
||||||
""".format(
|
""".format(
|
||||||
frappe.bold(max_reserved_qty), self.stock_uom
|
frappe.bold(allowed_qty),
|
||||||
|
self.stock_uom,
|
||||||
|
frappe.bold(self.item_code),
|
||||||
|
self.voucher_type,
|
||||||
|
frappe.bold(self.voucher_no),
|
||||||
|
actual_qty,
|
||||||
|
actual_qty - self.available_qty,
|
||||||
|
self.available_qty,
|
||||||
|
self.voucher_qty,
|
||||||
|
voucher_delivered_qty,
|
||||||
|
total_reserved_qty,
|
||||||
|
allowed_qty,
|
||||||
)
|
)
|
||||||
frappe.throw(msg)
|
frappe.throw(msg)
|
||||||
|
|
||||||
@ -509,7 +523,6 @@ def get_available_qty_to_reserve(
|
|||||||
"""Returns `Available Qty to Reserve (Actual Qty - Reserved Qty)` for Item, Warehouse and Batch combination."""
|
"""Returns `Available Qty to Reserve (Actual Qty - Reserved Qty)` for Item, Warehouse and Batch combination."""
|
||||||
|
|
||||||
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
||||||
from erpnext.stock.utils import get_stock_balance
|
|
||||||
|
|
||||||
if batch_no:
|
if batch_no:
|
||||||
return get_batch_qty(
|
return get_batch_qty(
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint, flt, getdate
|
from frappe.utils import cint, flt, get_table_name, getdate
|
||||||
from frappe.utils.deprecations import deprecated
|
from frappe.utils.deprecations import deprecated
|
||||||
from pypika import functions as fn
|
from pypika import functions as fn
|
||||||
|
|
||||||
@ -13,11 +13,22 @@ from erpnext.stock.doctype.warehouse.warehouse import apply_warehouse_filter
|
|||||||
SLE_COUNT_LIMIT = 10_000
|
SLE_COUNT_LIMIT = 10_000
|
||||||
|
|
||||||
|
|
||||||
|
def _estimate_table_row_count(doctype: str):
|
||||||
|
table = get_table_name(doctype)
|
||||||
|
return cint(
|
||||||
|
frappe.db.sql(
|
||||||
|
f"""select table_rows
|
||||||
|
from information_schema.tables
|
||||||
|
where table_name = '{table}' ;"""
|
||||||
|
)[0][0]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
if not filters:
|
if not filters:
|
||||||
filters = {}
|
filters = {}
|
||||||
|
|
||||||
sle_count = frappe.db.count("Stock Ledger Entry")
|
sle_count = _estimate_table_row_count("Stock Ledger Entry")
|
||||||
|
|
||||||
if sle_count > SLE_COUNT_LIMIT and not filters.get("item_code") and not filters.get("warehouse"):
|
if sle_count > SLE_COUNT_LIMIT and not filters.get("item_code") and not filters.get("warehouse"):
|
||||||
frappe.throw(_("Please select either the Item or Warehouse filter to generate the report."))
|
frappe.throw(_("Please select either the Item or Warehouse filter to generate the report."))
|
||||||
|
@ -149,6 +149,7 @@ frappe.query_reports["Reserved Stock"] = {
|
|||||||
formatter: (value, row, column, data, default_formatter) => {
|
formatter: (value, row, column, data, default_formatter) => {
|
||||||
value = default_formatter(value, row, column, data);
|
value = default_formatter(value, row, column, data);
|
||||||
|
|
||||||
|
if (data) {
|
||||||
if (column.fieldname == "status") {
|
if (column.fieldname == "status") {
|
||||||
switch (data.status) {
|
switch (data.status) {
|
||||||
case "Partially Reserved":
|
case "Partially Reserved":
|
||||||
@ -178,6 +179,7 @@ frappe.query_reports["Reserved Stock"] = {
|
|||||||
value = "<span style='color:red'>" + value + "</span>";
|
value = "<span style='color:red'>" + value + "</span>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
|
@ -218,9 +218,10 @@ class SerialBatchBundle:
|
|||||||
).validate_serial_and_batch_inventory()
|
).validate_serial_and_batch_inventory()
|
||||||
|
|
||||||
def post_process(self):
|
def post_process(self):
|
||||||
if not self.sle.serial_and_batch_bundle:
|
if not self.sle.serial_and_batch_bundle and not self.sle.serial_no and not self.sle.batch_no:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.sle.serial_and_batch_bundle:
|
||||||
docstatus = frappe.get_cached_value(
|
docstatus = frappe.get_cached_value(
|
||||||
"Serial and Batch Bundle", self.sle.serial_and_batch_bundle, "docstatus"
|
"Serial and Batch Bundle", self.sle.serial_and_batch_bundle, "docstatus"
|
||||||
)
|
)
|
||||||
@ -249,7 +250,12 @@ class SerialBatchBundle:
|
|||||||
doc.submit()
|
doc.submit()
|
||||||
|
|
||||||
def set_warehouse_and_status_in_serial_nos(self):
|
def set_warehouse_and_status_in_serial_nos(self):
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos as get_parsed_serial_nos
|
||||||
|
|
||||||
serial_nos = get_serial_nos(self.sle.serial_and_batch_bundle)
|
serial_nos = get_serial_nos(self.sle.serial_and_batch_bundle)
|
||||||
|
if not self.sle.serial_and_batch_bundle and self.sle.serial_no:
|
||||||
|
serial_nos = get_parsed_serial_nos(self.sle.serial_no)
|
||||||
|
|
||||||
warehouse = self.warehouse if self.sle.actual_qty > 0 else None
|
warehouse = self.warehouse if self.sle.actual_qty > 0 else None
|
||||||
|
|
||||||
if not serial_nos:
|
if not serial_nos:
|
||||||
@ -263,7 +269,14 @@ class SerialBatchBundle:
|
|||||||
(
|
(
|
||||||
frappe.qb.update(sn_table)
|
frappe.qb.update(sn_table)
|
||||||
.set(sn_table.warehouse, warehouse)
|
.set(sn_table.warehouse, warehouse)
|
||||||
.set(sn_table.status, "Active" if warehouse else status)
|
.set(
|
||||||
|
sn_table.status,
|
||||||
|
"Active"
|
||||||
|
if warehouse
|
||||||
|
else status
|
||||||
|
if (sn_table.purchase_document_no != self.sle.voucher_no and self.sle.is_cancelled != 1)
|
||||||
|
else "Inactive",
|
||||||
|
)
|
||||||
.where(sn_table.name.isin(serial_nos))
|
.where(sn_table.name.isin(serial_nos))
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
@ -290,6 +303,8 @@ class SerialBatchBundle:
|
|||||||
from erpnext.stock.doctype.batch.batch import get_available_batches
|
from erpnext.stock.doctype.batch.batch import get_available_batches
|
||||||
|
|
||||||
batches = get_batch_nos(self.sle.serial_and_batch_bundle)
|
batches = get_batch_nos(self.sle.serial_and_batch_bundle)
|
||||||
|
if not self.sle.serial_and_batch_bundle and self.sle.batch_no:
|
||||||
|
batches = frappe._dict({self.sle.batch_no: self.sle.actual_qty})
|
||||||
|
|
||||||
batches_qty = get_available_batches(
|
batches_qty = get_available_batches(
|
||||||
frappe._dict(
|
frappe._dict(
|
||||||
@ -312,13 +327,35 @@ def get_serial_nos(serial_and_batch_bundle, serial_nos=None):
|
|||||||
if serial_nos:
|
if serial_nos:
|
||||||
filters["serial_no"] = ("in", serial_nos)
|
filters["serial_no"] = ("in", serial_nos)
|
||||||
|
|
||||||
entries = frappe.get_all("Serial and Batch Entry", fields=["serial_no"], filters=filters)
|
entries = frappe.get_all(
|
||||||
|
"Serial and Batch Entry", fields=["serial_no"], filters=filters, order_by="idx"
|
||||||
|
)
|
||||||
if not entries:
|
if not entries:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
return [d.serial_no for d in entries if d.serial_no]
|
return [d.serial_no for d in entries if d.serial_no]
|
||||||
|
|
||||||
|
|
||||||
|
def get_batches_from_bundle(serial_and_batch_bundle, batches=None):
|
||||||
|
if not serial_and_batch_bundle:
|
||||||
|
return []
|
||||||
|
|
||||||
|
filters = {"parent": serial_and_batch_bundle, "batch_no": ("is", "set")}
|
||||||
|
if isinstance(serial_and_batch_bundle, list):
|
||||||
|
filters = {"parent": ("in", serial_and_batch_bundle)}
|
||||||
|
|
||||||
|
if batches:
|
||||||
|
filters["batch_no"] = ("in", batches)
|
||||||
|
|
||||||
|
entries = frappe.get_all(
|
||||||
|
"Serial and Batch Entry", fields=["batch_no", "qty"], filters=filters, order_by="idx", as_list=1
|
||||||
|
)
|
||||||
|
if not entries:
|
||||||
|
return frappe._dict({})
|
||||||
|
|
||||||
|
return frappe._dict(entries)
|
||||||
|
|
||||||
|
|
||||||
def get_serial_nos_from_bundle(serial_and_batch_bundle, serial_nos=None):
|
def get_serial_nos_from_bundle(serial_and_batch_bundle, serial_nos=None):
|
||||||
return get_serial_nos(serial_and_batch_bundle, serial_nos=serial_nos)
|
return get_serial_nos(serial_and_batch_bundle, serial_nos=serial_nos)
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry impor
|
|||||||
)
|
)
|
||||||
from erpnext.stock.utils import (
|
from erpnext.stock.utils import (
|
||||||
get_incoming_outgoing_rate_for_cancel,
|
get_incoming_outgoing_rate_for_cancel,
|
||||||
|
get_incoming_rate,
|
||||||
get_or_make_bin,
|
get_or_make_bin,
|
||||||
get_stock_balance,
|
get_stock_balance,
|
||||||
get_valuation_method,
|
get_valuation_method,
|
||||||
@ -841,6 +842,26 @@ class update_entries_after(object):
|
|||||||
get_rate_for_return, # don't move this import to top
|
get_rate_for_return, # don't move this import to top
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.valuation_method == "Moving Average":
|
||||||
|
rate = get_incoming_rate(
|
||||||
|
{
|
||||||
|
"item_code": sle.item_code,
|
||||||
|
"warehouse": sle.warehouse,
|
||||||
|
"posting_date": sle.posting_date,
|
||||||
|
"posting_time": sle.posting_time,
|
||||||
|
"qty": sle.actual_qty,
|
||||||
|
"serial_no": sle.get("serial_no"),
|
||||||
|
"batch_no": sle.get("batch_no"),
|
||||||
|
"serial_and_batch_bundle": sle.get("serial_and_batch_bundle"),
|
||||||
|
"company": sle.company,
|
||||||
|
"voucher_type": sle.voucher_type,
|
||||||
|
"voucher_no": sle.voucher_no,
|
||||||
|
"allow_zero_valuation": self.allow_zero_rate,
|
||||||
|
"sle": sle.name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
rate = get_rate_for_return(
|
rate = get_rate_for_return(
|
||||||
sle.voucher_type,
|
sle.voucher_type,
|
||||||
sle.voucher_no,
|
sle.voucher_no,
|
||||||
@ -848,7 +869,6 @@ class update_entries_after(object):
|
|||||||
voucher_detail_no=sle.voucher_detail_no,
|
voucher_detail_no=sle.voucher_detail_no,
|
||||||
sle=sle,
|
sle=sle,
|
||||||
)
|
)
|
||||||
|
|
||||||
elif (
|
elif (
|
||||||
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
|
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
|
||||||
and sle.voucher_detail_no
|
and sle.voucher_detail_no
|
||||||
|
@ -167,13 +167,13 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
)
|
)
|
||||||
self.update_status_updater_args()
|
self.update_status_updater_args()
|
||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
self.delete_auto_created_batches()
|
|
||||||
self.set_consumed_qty_in_subcontract_order()
|
self.set_consumed_qty_in_subcontract_order()
|
||||||
self.set_subcontracting_order_status()
|
self.set_subcontracting_order_status()
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
self.make_gl_entries_on_cancel()
|
self.make_gl_entries_on_cancel()
|
||||||
self.repost_future_sle_and_gle()
|
self.repost_future_sle_and_gle()
|
||||||
self.update_status()
|
self.update_status()
|
||||||
|
self.delete_auto_created_batches()
|
||||||
|
|
||||||
def validate_items_qty(self):
|
def validate_items_qty(self):
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
|
@ -365,24 +365,17 @@ class TestSubcontractingReceipt(FrappeTestCase):
|
|||||||
fg_warehouse_ac = get_inventory_account(scr.company, scr.items[0].warehouse)
|
fg_warehouse_ac = get_inventory_account(scr.company, scr.items[0].warehouse)
|
||||||
supplier_warehouse_ac = get_inventory_account(scr.company, scr.supplier_warehouse)
|
supplier_warehouse_ac = get_inventory_account(scr.company, scr.supplier_warehouse)
|
||||||
expense_account = scr.items[0].expense_account
|
expense_account = scr.items[0].expense_account
|
||||||
|
expected_values = [
|
||||||
|
[fg_warehouse_ac, 2100.0, 0.0], # FG Amount (D)
|
||||||
|
[supplier_warehouse_ac, 0.0, 1000.0], # RM Cost (C)
|
||||||
|
[additional_costs_expense_account, 0.0, 100.0], # Additional Cost (C)
|
||||||
|
[expense_account, 0.0, 1000.0], # Service Cost (C)
|
||||||
|
]
|
||||||
|
|
||||||
if fg_warehouse_ac == supplier_warehouse_ac:
|
for i in range(len(expected_values)):
|
||||||
expected_values = {
|
self.assertEqual(expected_values[i][0], gl_entries[i]["account"])
|
||||||
fg_warehouse_ac: [2100.0, 1000.0], # FG Amount (D), RM Cost (C)
|
self.assertEqual(expected_values[i][1], gl_entries[i]["debit"])
|
||||||
expense_account: [0.0, 1000.0], # Service Cost (C)
|
self.assertEqual(expected_values[i][2], gl_entries[i]["credit"])
|
||||||
additional_costs_expense_account: [0.0, 100.0], # Additional Cost (C)
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
expected_values = {
|
|
||||||
fg_warehouse_ac: [2100.0, 0.0], # FG Amount (D)
|
|
||||||
supplier_warehouse_ac: [0.0, 1000.0], # RM Cost (C)
|
|
||||||
expense_account: [0.0, 1000.0], # Service Cost (C)
|
|
||||||
additional_costs_expense_account: [0.0, 100.0], # Additional Cost (C)
|
|
||||||
}
|
|
||||||
|
|
||||||
for gle in gl_entries:
|
|
||||||
self.assertEqual(expected_values[gle.account][0], gle.debit)
|
|
||||||
self.assertEqual(expected_values[gle.account][1], gle.credit)
|
|
||||||
|
|
||||||
scr.reload()
|
scr.reload()
|
||||||
scr.cancel()
|
scr.cancel()
|
||||||
@ -953,6 +946,91 @@ class TestSubcontractingReceipt(FrappeTestCase):
|
|||||||
|
|
||||||
scr.submit()
|
scr.submit()
|
||||||
|
|
||||||
|
def test_subcontracting_receipt_cancel_with_batch(self):
|
||||||
|
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||||
|
|
||||||
|
# Step - 1: Set Backflush Based On as "BOM"
|
||||||
|
set_backflush_based_on("BOM")
|
||||||
|
|
||||||
|
# Step - 2: Create FG and RM Items
|
||||||
|
fg_item = make_item(
|
||||||
|
properties={"is_stock_item": 1, "is_sub_contracted_item": 1, "has_batch_no": 1}
|
||||||
|
).name
|
||||||
|
rm_item1 = make_item(properties={"is_stock_item": 1}).name
|
||||||
|
rm_item2 = make_item(properties={"is_stock_item": 1}).name
|
||||||
|
make_item("Subcontracted Service Item Test For Batch 1", {"is_stock_item": 0})
|
||||||
|
|
||||||
|
# Step - 3: Create BOM for FG Item
|
||||||
|
bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2])
|
||||||
|
for rm_item in bom.items:
|
||||||
|
self.assertEqual(rm_item.rate, 0)
|
||||||
|
self.assertEqual(rm_item.amount, 0)
|
||||||
|
bom = bom.name
|
||||||
|
|
||||||
|
# Step - 4: Create PO and SCO
|
||||||
|
service_items = [
|
||||||
|
{
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"item_code": "Subcontracted Service Item Test For Batch 1",
|
||||||
|
"qty": 100,
|
||||||
|
"rate": 100,
|
||||||
|
"fg_item": fg_item,
|
||||||
|
"fg_item_qty": 100,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
sco = get_subcontracting_order(service_items=service_items)
|
||||||
|
for rm_item in sco.supplied_items:
|
||||||
|
self.assertEqual(rm_item.rate, 0)
|
||||||
|
self.assertEqual(rm_item.amount, 0)
|
||||||
|
|
||||||
|
# Step - 5: Inward Raw Materials
|
||||||
|
rm_items = get_rm_items(sco.supplied_items)
|
||||||
|
for rm_item in rm_items:
|
||||||
|
rm_item["rate"] = 100
|
||||||
|
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||||
|
|
||||||
|
# Step - 6: Transfer RM's to Subcontractor
|
||||||
|
se = make_stock_transfer_entry(
|
||||||
|
sco_no=sco.name,
|
||||||
|
rm_items=rm_items,
|
||||||
|
itemwise_details=copy.deepcopy(itemwise_details),
|
||||||
|
)
|
||||||
|
for item in se.items:
|
||||||
|
self.assertEqual(item.qty, 100)
|
||||||
|
self.assertEqual(item.basic_rate, 100)
|
||||||
|
self.assertEqual(item.amount, item.qty * item.basic_rate)
|
||||||
|
|
||||||
|
batch_doc = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Batch",
|
||||||
|
"item": fg_item,
|
||||||
|
"batch_id": frappe.generate_hash(length=10),
|
||||||
|
}
|
||||||
|
).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
serial_batch_bundle = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Serial and Batch Bundle",
|
||||||
|
"item_code": fg_item,
|
||||||
|
"warehouse": sco.items[0].warehouse,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"type_of_transaction": "Inward",
|
||||||
|
"voucher_type": "Subcontracting Receipt",
|
||||||
|
"entries": [{"batch_no": batch_doc.name, "qty": 100}],
|
||||||
|
}
|
||||||
|
).insert(ignore_permissions=True)
|
||||||
|
|
||||||
|
# Step - 7: Create Subcontracting Receipt
|
||||||
|
scr = make_subcontracting_receipt(sco.name)
|
||||||
|
scr.items[0].serial_and_batch_bundle = serial_batch_bundle.name
|
||||||
|
scr.save()
|
||||||
|
scr.submit()
|
||||||
|
scr.load_from_db()
|
||||||
|
|
||||||
|
# Step - 8: Cancel Subcontracting Receipt
|
||||||
|
scr.cancel()
|
||||||
|
self.assertTrue(scr.docstatus == 2)
|
||||||
|
|
||||||
@change_settings("Buying Settings", {"auto_create_purchase_receipt": 1})
|
@change_settings("Buying Settings", {"auto_create_purchase_receipt": 1})
|
||||||
def test_auto_create_purchase_receipt(self):
|
def test_auto_create_purchase_receipt(self):
|
||||||
fg_item = "Subcontracted Item SA1"
|
fg_item = "Subcontracted Item SA1"
|
||||||
|
@ -58,7 +58,9 @@ frappe.ui.form.on("Issue", {
|
|||||||
|
|
||||||
frappe.call("erpnext.support.doctype.service_level_agreement.service_level_agreement.reset_service_level_agreement", {
|
frappe.call("erpnext.support.doctype.service_level_agreement.service_level_agreement.reset_service_level_agreement", {
|
||||||
reason: values.reason,
|
reason: values.reason,
|
||||||
user: frappe.session.user_email
|
user: frappe.session.user_email,
|
||||||
|
doctype: frm.doc.doctype,
|
||||||
|
docname: frm.doc.name,
|
||||||
}, () => {
|
}, () => {
|
||||||
reset_sla.enable_primary_action();
|
reset_sla.enable_primary_action();
|
||||||
frm.refresh();
|
frm.refresh();
|
||||||
|
@ -774,10 +774,12 @@ def get_response_and_resolution_duration(doc):
|
|||||||
return priority
|
return priority
|
||||||
|
|
||||||
|
|
||||||
def reset_service_level_agreement(doc, reason, user):
|
@frappe.whitelist()
|
||||||
|
def reset_service_level_agreement(doctype: str, docname: str, reason, user):
|
||||||
if not frappe.db.get_single_value("Support Settings", "allow_resetting_service_level_agreement"):
|
if not frappe.db.get_single_value("Support Settings", "allow_resetting_service_level_agreement"):
|
||||||
frappe.throw(_("Allow Resetting Service Level Agreement from Support Settings."))
|
frappe.throw(_("Allow Resetting Service Level Agreement from Support Settings."))
|
||||||
|
|
||||||
|
doc = frappe.get_doc(doctype, docname)
|
||||||
frappe.get_doc(
|
frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "Comment",
|
"doctype": "Comment",
|
||||||
|
24
erpnext/tests/test_perf.py
Normal file
24
erpnext/tests/test_perf.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
INDEXED_FIELDS = {
|
||||||
|
"Bin": ["item_code"],
|
||||||
|
"GL Entry": ["voucher_type", "against_voucher_type"],
|
||||||
|
"Purchase Order Item": ["item_code"],
|
||||||
|
"Stock Ledger Entry": ["warehouse"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestPerformance(FrappeTestCase):
|
||||||
|
def test_ensure_indexes(self):
|
||||||
|
# These fields are not explicitly indexed BUT they are prefix in some
|
||||||
|
# other composite index. If those are removed this test should be
|
||||||
|
# updated accordingly.
|
||||||
|
for doctype, fields in INDEXED_FIELDS.items():
|
||||||
|
for field in fields:
|
||||||
|
self.assertTrue(
|
||||||
|
frappe.db.sql(
|
||||||
|
f"""SHOW INDEX FROM `tab{doctype}`
|
||||||
|
WHERE Column_name = "{field}" AND Seq_in_index = 1"""
|
||||||
|
)
|
||||||
|
)
|
@ -103,6 +103,7 @@ Actual Qty is mandatory,Die tatsächliche Menge ist zwingend erforderlich,
|
|||||||
Actual Qty {0} / Waiting Qty {1},Tatsächliche Menge {0} / Wartezeit {1},
|
Actual Qty {0} / Waiting Qty {1},Tatsächliche Menge {0} / Wartezeit {1},
|
||||||
Actual Qty: Quantity available in the warehouse.,Tatsächliche Menge: Menge verfügbar im Lager.,
|
Actual Qty: Quantity available in the warehouse.,Tatsächliche Menge: Menge verfügbar im Lager.,
|
||||||
Actual qty in stock,Tatsächliche Menge auf Lager,
|
Actual qty in stock,Tatsächliche Menge auf Lager,
|
||||||
|
Actual Time (in Hours via Time Sheet), IST Zeit (in Stunden aus Zeiterfassung),
|
||||||
Actual type tax cannot be included in Item rate in row {0},Tatsächliche Steuerart kann nicht im Artikelpreis in Zeile {0} beinhaltet sein,
|
Actual type tax cannot be included in Item rate in row {0},Tatsächliche Steuerart kann nicht im Artikelpreis in Zeile {0} beinhaltet sein,
|
||||||
Add,Hinzufügen,
|
Add,Hinzufügen,
|
||||||
Add / Edit Prices,Preise hinzufügen / bearbeiten,
|
Add / Edit Prices,Preise hinzufügen / bearbeiten,
|
||||||
|
Can't render this file because it is too large.
|
Loading…
x
Reference in New Issue
Block a user