Compare commits
1 Commits
v15.48.3
...
automatch-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e5cf18066 |
@@ -4,7 +4,7 @@ import inspect
|
||||
import frappe
|
||||
from frappe.utils.user import is_website_user
|
||||
|
||||
__version__ = "15.48.3"
|
||||
__version__ = "15.45.4"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -120,7 +120,6 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
|
||||
args: {
|
||||
bank_account: frm.doc.bank_account,
|
||||
till_date: frappe.datetime.add_days(frm.doc.bank_statement_from_date, -1),
|
||||
company: frm.doc.company,
|
||||
},
|
||||
callback: (response) => {
|
||||
frm.set_value("account_opening_balance", response.message);
|
||||
@@ -136,7 +135,6 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
|
||||
args: {
|
||||
bank_account: frm.doc.bank_account,
|
||||
till_date: frm.doc.bank_statement_to_date,
|
||||
company: frm.doc.company,
|
||||
},
|
||||
callback: (response) => {
|
||||
frm.cleared_balance = response.message;
|
||||
|
||||
@@ -79,17 +79,10 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_account_balance(bank_account, till_date, company):
|
||||
def get_account_balance(bank_account, till_date):
|
||||
# returns account balance till the specified date
|
||||
account = frappe.db.get_value("Bank Account", bank_account, "account")
|
||||
filters = frappe._dict(
|
||||
{
|
||||
"account": account,
|
||||
"report_date": till_date,
|
||||
"include_pos_transactions": 1,
|
||||
"company": company,
|
||||
}
|
||||
)
|
||||
filters = frappe._dict({"account": account, "report_date": till_date, "include_pos_transactions": 1})
|
||||
data = get_entries(filters)
|
||||
|
||||
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
|
||||
@@ -101,7 +94,11 @@ def get_account_balance(bank_account, till_date, company):
|
||||
|
||||
amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters)
|
||||
|
||||
return flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) + amounts_not_reflected_in_system
|
||||
bank_bal = (
|
||||
flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) + amounts_not_reflected_in_system
|
||||
)
|
||||
|
||||
return bank_bal
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -49,41 +49,39 @@ class AutoMatchbyAccountIBAN:
|
||||
return result
|
||||
|
||||
def match_account_in_party(self) -> tuple | None:
|
||||
"""Check if there is a IBAN/Account No. match in Customer/Supplier/Employee"""
|
||||
result = None
|
||||
parties = get_parties_in_order(self.deposit)
|
||||
or_filters = self.get_or_filters()
|
||||
"""
|
||||
Returns (Party Type, Party) if a matching account is found in Bank Account or Employee:
|
||||
1. Get party from a matching (iban/account no) Bank Account
|
||||
2. If not found, get party from Employee with matching bank account details (iban/account no)
|
||||
"""
|
||||
if not (self.bank_party_account_number or self.bank_party_iban):
|
||||
# Nothing to match
|
||||
return None
|
||||
|
||||
for party in parties:
|
||||
party_result = frappe.db.get_all(
|
||||
"Bank Account", or_filters=or_filters, pluck="party", limit_page_length=1
|
||||
)
|
||||
# Search for a matching Bank Account that has party set
|
||||
party_result = frappe.db.get_all(
|
||||
"Bank Account",
|
||||
or_filters=self.get_or_filters(),
|
||||
filters={"party_type": ("is", "set"), "party": ("is", "set")},
|
||||
fields=["party", "party_type"],
|
||||
limit_page_length=1,
|
||||
)
|
||||
if result := party_result[0] if party_result else None:
|
||||
return (result["party_type"], result["party"])
|
||||
|
||||
if party == "Employee" and not party_result:
|
||||
# Search in Bank Accounts first for Employee, and then Employee record
|
||||
if "bank_account_no" in or_filters:
|
||||
or_filters["bank_ac_no"] = or_filters.pop("bank_account_no")
|
||||
# If no party is found, search in Employee (since it has bank account details)
|
||||
employee_result = frappe.db.get_all(
|
||||
"Employee", or_filters=self.get_or_filters("Employee"), pluck="name", limit_page_length=1
|
||||
)
|
||||
if employee_result:
|
||||
return ("Employee", employee_result[0])
|
||||
|
||||
party_result = frappe.db.get_all(
|
||||
party, or_filters=or_filters, pluck="name", limit_page_length=1
|
||||
)
|
||||
|
||||
if "bank_ac_no" in or_filters:
|
||||
or_filters["bank_account_no"] = or_filters.pop("bank_ac_no")
|
||||
|
||||
if party_result:
|
||||
result = (
|
||||
party,
|
||||
party_result[0],
|
||||
)
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
def get_or_filters(self) -> dict:
|
||||
def get_or_filters(self, party: str | None = None) -> dict:
|
||||
"""Return OR filters for Bank Account and IBAN"""
|
||||
or_filters = {}
|
||||
if self.bank_party_account_number:
|
||||
or_filters["bank_account_no"] = self.bank_party_account_number
|
||||
bank_ac_field = "bank_ac_no" if party == "Employee" else "bank_account_no"
|
||||
or_filters[bank_ac_field] = self.bank_party_account_number
|
||||
|
||||
if self.bank_party_iban:
|
||||
or_filters["iban"] = self.bank_party_iban
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "format:ACC-PPR-{#####}",
|
||||
"beta": 1,
|
||||
"creation": "2023-03-30 21:28:39.793927",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
@@ -157,7 +158,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-01-08 08:22:14.798085",
|
||||
"modified": "2024-08-27 14:48:56.715320",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Payment Reconciliation",
|
||||
@@ -191,4 +192,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "company"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ def trigger_reconciliation_for_queued_docs():
|
||||
unique_filters = set()
|
||||
queue_size = 5
|
||||
|
||||
fields = ["company", "party_type", "party", "receivable_payable_account", "default_advance_account"]
|
||||
fields = ["company", "party_type", "party", "receivable_payable_account"]
|
||||
|
||||
def get_filters_as_tuple(fields, doc):
|
||||
filters = ()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "format:PPR-LOG-{##}",
|
||||
"beta": 1,
|
||||
"creation": "2023-03-13 15:00:09.149681",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
@@ -109,7 +110,7 @@
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-01-08 08:22:19.104975",
|
||||
"modified": "2023-11-02 11:32:12.254018",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Payment Reconciliation Log",
|
||||
|
||||
@@ -994,51 +994,47 @@ frappe.ui.form.on("Sales Invoice", {
|
||||
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.docstatus === 0 && !frm.doc.is_return) {
|
||||
frm.add_custom_button(
|
||||
__("Timesheet"),
|
||||
function () {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __("Fetch Timesheet"),
|
||||
fields: [
|
||||
{
|
||||
label: __("From"),
|
||||
fieldname: "from_time",
|
||||
fieldtype: "Date",
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldtype: "Column Break",
|
||||
fieldname: "col_break_1",
|
||||
},
|
||||
{
|
||||
label: __("To"),
|
||||
fieldname: "to_time",
|
||||
fieldtype: "Date",
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
label: __("Project"),
|
||||
fieldname: "project",
|
||||
fieldtype: "Link",
|
||||
options: "Project",
|
||||
default: frm.doc.project,
|
||||
},
|
||||
],
|
||||
primary_action: function () {
|
||||
const data = d.get_values();
|
||||
frm.events.add_timesheet_data(frm, {
|
||||
from_time: data.from_time,
|
||||
to_time: data.to_time,
|
||||
project: data.project,
|
||||
});
|
||||
d.hide();
|
||||
frm.add_custom_button(__("Fetch Timesheet"), function () {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __("Fetch Timesheet"),
|
||||
fields: [
|
||||
{
|
||||
label: __("From"),
|
||||
fieldname: "from_time",
|
||||
fieldtype: "Date",
|
||||
reqd: 1,
|
||||
},
|
||||
primary_action_label: __("Get Timesheets"),
|
||||
});
|
||||
d.show();
|
||||
},
|
||||
__("Get Items From")
|
||||
);
|
||||
{
|
||||
fieldtype: "Column Break",
|
||||
fieldname: "col_break_1",
|
||||
},
|
||||
{
|
||||
label: __("To"),
|
||||
fieldname: "to_time",
|
||||
fieldtype: "Date",
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
label: __("Project"),
|
||||
fieldname: "project",
|
||||
fieldtype: "Link",
|
||||
options: "Project",
|
||||
default: frm.doc.project,
|
||||
},
|
||||
],
|
||||
primary_action: function () {
|
||||
const data = d.get_values();
|
||||
frm.events.add_timesheet_data(frm, {
|
||||
from_time: data.from_time,
|
||||
to_time: data.to_time,
|
||||
project: data.project,
|
||||
});
|
||||
d.hide();
|
||||
},
|
||||
primary_action_label: __("Get Timesheets"),
|
||||
});
|
||||
d.show();
|
||||
});
|
||||
}
|
||||
|
||||
if (frm.doc.is_debit_note) {
|
||||
|
||||
@@ -39,7 +39,7 @@ from erpnext.controllers.selling_controller import SellingController
|
||||
from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
|
||||
from erpnext.setup.doctype.company.company import update_company_current_month_sales
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no, get_serial_nos
|
||||
|
||||
form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
|
||||
|
||||
@@ -321,7 +321,9 @@ class SalesInvoice(SellingController):
|
||||
self.set_against_income_account()
|
||||
self.validate_time_sheets_are_submitted()
|
||||
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount")
|
||||
if self.is_return:
|
||||
if not self.is_return:
|
||||
self.validate_serial_numbers()
|
||||
else:
|
||||
self.timesheets = []
|
||||
self.update_packing_list()
|
||||
self.set_billing_hours_and_amount()
|
||||
@@ -1704,6 +1706,53 @@ class SalesInvoice(SellingController):
|
||||
self.set("write_off_amount", reference_doc.get("write_off_amount"))
|
||||
self.due_date = None
|
||||
|
||||
def validate_serial_numbers(self):
|
||||
"""
|
||||
validate serial number agains Delivery Note and Sales Invoice
|
||||
"""
|
||||
self.set_serial_no_against_delivery_note()
|
||||
self.validate_serial_against_delivery_note()
|
||||
|
||||
def set_serial_no_against_delivery_note(self):
|
||||
for item in self.items:
|
||||
if item.serial_no and item.delivery_note and item.qty != len(get_serial_nos(item.serial_no)):
|
||||
item.serial_no = get_delivery_note_serial_no(item.item_code, item.qty, item.delivery_note)
|
||||
|
||||
def validate_serial_against_delivery_note(self):
|
||||
"""
|
||||
validate if the serial numbers in Sales Invoice Items are same as in
|
||||
Delivery Note Item
|
||||
"""
|
||||
|
||||
for item in self.items:
|
||||
if not item.delivery_note or not item.dn_detail:
|
||||
continue
|
||||
|
||||
serial_nos = frappe.db.get_value("Delivery Note Item", item.dn_detail, "serial_no") or ""
|
||||
dn_serial_nos = set(get_serial_nos(serial_nos))
|
||||
|
||||
serial_nos = item.serial_no or ""
|
||||
si_serial_nos = set(get_serial_nos(serial_nos))
|
||||
serial_no_diff = si_serial_nos - dn_serial_nos
|
||||
|
||||
if serial_no_diff:
|
||||
dn_link = frappe.utils.get_link_to_form("Delivery Note", item.delivery_note)
|
||||
serial_no_msg = ", ".join(frappe.bold(d) for d in serial_no_diff)
|
||||
|
||||
msg = _("Row #{0}: The following Serial Nos are not present in Delivery Note {1}:").format(
|
||||
item.idx, dn_link
|
||||
)
|
||||
msg += " " + serial_no_msg
|
||||
|
||||
frappe.throw(msg=msg, title=_("Serial Nos Mismatch"))
|
||||
|
||||
if item.serial_no and cint(item.qty) != len(si_serial_nos):
|
||||
frappe.throw(
|
||||
_("Row #{0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format(
|
||||
item.idx, item.qty, item.item_code, len(si_serial_nos)
|
||||
)
|
||||
)
|
||||
|
||||
def update_project(self):
|
||||
unique_projects = list(set([d.project for d in self.get("items") if d.project]))
|
||||
if self.project and self.project not in unique_projects:
|
||||
|
||||
@@ -378,7 +378,7 @@
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"no_copy": 1,
|
||||
"options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt\nCapitalized\nDecapitalized\nWork In Progress",
|
||||
"options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt\nCapitalized\nDecapitalized",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -595,7 +595,7 @@
|
||||
"link_fieldname": "target_asset"
|
||||
}
|
||||
],
|
||||
"modified": "2024-12-26 14:23:20.968882",
|
||||
"modified": "2024-08-26 23:28:29.095139",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
|
||||
@@ -112,7 +112,6 @@ class Asset(AccountsController):
|
||||
"Receipt",
|
||||
"Capitalized",
|
||||
"Decapitalized",
|
||||
"Work In Progress",
|
||||
]
|
||||
supplier: DF.Link | None
|
||||
total_asset_cost: DF.Currency
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
frappe.listview_settings["Asset"] = {
|
||||
add_fields: ["status", "docstatus"],
|
||||
has_indicator_for_draft: 1,
|
||||
add_fields: ["status"],
|
||||
get_indicator: function (doc) {
|
||||
if (doc.status === "Fully Depreciated") {
|
||||
return [__("Fully Depreciated"), "green", "status,=,Fully Depreciated"];
|
||||
@@ -8,8 +7,6 @@ frappe.listview_settings["Asset"] = {
|
||||
return [__("Partially Depreciated"), "grey", "status,=,Partially Depreciated"];
|
||||
} else if (doc.status === "Sold") {
|
||||
return [__("Sold"), "green", "status,=,Sold"];
|
||||
} else if (doc.status === "Work In Progress") {
|
||||
return [__("Work In Progress"), "orange", "status,=,Work In Progress"];
|
||||
} else if (["Capitalized", "Decapitalized"].includes(doc.status)) {
|
||||
return [__(doc.status), "grey", "status,=," + doc.status];
|
||||
} else if (doc.status === "Scrapped") {
|
||||
@@ -24,7 +21,7 @@ frappe.listview_settings["Asset"] = {
|
||||
return [__("Receipt"), "green", "status,=,Receipt"];
|
||||
} else if (doc.status === "Submitted") {
|
||||
return [__("Submitted"), "blue", "status,=,Submitted"];
|
||||
} else if (doc.status === "Draft" || doc.docstatus === 0) {
|
||||
} else if (doc.status === "Draft") {
|
||||
return [__("Draft"), "red", "status,=,Draft"];
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1725,10 +1725,6 @@ def create_asset(**args):
|
||||
},
|
||||
)
|
||||
|
||||
if asset.is_composite_asset:
|
||||
asset.gross_purchase_amount = 0
|
||||
asset.purchase_amount = 0
|
||||
|
||||
if not args.do_not_save:
|
||||
try:
|
||||
asset.insert(ignore_if_duplicate=True)
|
||||
|
||||
@@ -638,7 +638,6 @@ class AssetCapitalization(StockController):
|
||||
self.target_fixed_asset_account = get_asset_category_account(
|
||||
"fixed_asset_account", item=self.target_item_code, company=asset_doc.company
|
||||
)
|
||||
asset_doc.set_status("Work In Progress")
|
||||
|
||||
add_asset_activity(
|
||||
asset_doc.name,
|
||||
@@ -663,9 +662,8 @@ class AssetCapitalization(StockController):
|
||||
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
|
||||
|
||||
asset_doc = frappe.get_doc("Asset", self.target_asset)
|
||||
asset_doc.gross_purchase_amount += total_target_asset_value
|
||||
asset_doc.purchase_amount += total_target_asset_value
|
||||
asset_doc.set_status("Work In Progress")
|
||||
asset_doc.gross_purchase_amount = total_target_asset_value
|
||||
asset_doc.purchase_amount = total_target_asset_value
|
||||
asset_doc.flags.ignore_validate = True
|
||||
asset_doc.save()
|
||||
|
||||
|
||||
@@ -96,7 +96,6 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset)
|
||||
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
|
||||
self.assertEqual(target_asset.purchase_amount, total_amount)
|
||||
self.assertEqual(target_asset.status, "Work In Progress")
|
||||
|
||||
# Test Consumed Asset values
|
||||
self.assertEqual(consumed_asset.db_get("status"), "Capitalized")
|
||||
@@ -271,7 +270,6 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset)
|
||||
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
|
||||
self.assertEqual(target_asset.purchase_amount, total_amount)
|
||||
self.assertEqual(target_asset.status, "Work In Progress")
|
||||
|
||||
# Test General Ledger Entries
|
||||
expected_gle = {
|
||||
|
||||
@@ -13,7 +13,6 @@ from frappe.utils import (
|
||||
flt,
|
||||
get_first_day,
|
||||
get_last_day,
|
||||
get_link_to_form,
|
||||
getdate,
|
||||
is_last_day_of_the_month,
|
||||
month_diff,
|
||||
@@ -1063,7 +1062,7 @@ def make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
||||
if not current_asset_depr_schedule_doc:
|
||||
frappe.throw(
|
||||
_("Asset Depreciation Schedule not found for Asset {0} and Finance Book {1}").format(
|
||||
get_link_to_form("Asset", asset_doc.name), row.finance_book
|
||||
asset_doc.name, row.finance_book
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1109,7 +1108,7 @@ def get_temp_asset_depr_schedule_doc(
|
||||
if not current_asset_depr_schedule_doc:
|
||||
frappe.throw(
|
||||
_("Asset Depreciation Schedule not found for Asset {0} and Finance Book {1}").format(
|
||||
get_link_to_form("Asset", asset_doc.name), row.finance_book
|
||||
asset_doc.name, row.finance_book
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1257,7 +1257,6 @@ def add_items_in_ste(ste_doc, row, qty, rm_details, rm_detail_field="sco_rm_deta
|
||||
"item_code": row.item_details["rm_item_code"],
|
||||
"subcontracted_item": row.item_details["main_item_code"],
|
||||
"serial_no": "\n".join(row.serial_no) if row.serial_no else "",
|
||||
"use_serial_batch_fields": 1,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1298,13 +1297,10 @@ def make_return_stock_entry_for_subcontract(
|
||||
if not value.qty:
|
||||
continue
|
||||
|
||||
if item_details := value.get("item_details"):
|
||||
item_details["serial_and_batch_bundle"] = None
|
||||
|
||||
if value.batch_no:
|
||||
for batch_no, qty in value.batch_no.items():
|
||||
if qty > 0:
|
||||
add_items_in_ste(ste_doc, value, qty, rm_details, rm_detail_field, batch_no)
|
||||
add_items_in_ste(ste_doc, value, value.qty, rm_details, rm_detail_field, batch_no)
|
||||
else:
|
||||
add_items_in_ste(ste_doc, value, value.qty, rm_details, rm_detail_field)
|
||||
|
||||
|
||||
@@ -282,79 +282,6 @@ class TestSubcontractingController(FrappeTestCase):
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
|
||||
|
||||
def test_return_non_consumed_batch_materials(self):
|
||||
"""
|
||||
- Set backflush based on Material Transfer.
|
||||
- Create SCO for item Subcontracted Item SA2.
|
||||
- Transfer the batched components from Stores to Supplier warehouse with serial nos.
|
||||
- Transfer extra qty of component for the subcontracted item Subcontracted Item SA2.
|
||||
- Create SCR for full qty against the SCO and change the qty of raw material.
|
||||
- After that return the non consumed material back to the store from supplier's warehouse.
|
||||
"""
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
|
||||
set_backflush_based_on("Material Transferred for Subcontract")
|
||||
service_item = make_item("Subcontracted Service FG Item A", properties={"is_stock_item": 0}).name
|
||||
fg_item = make_item(
|
||||
"Subcontracted FG Item SA2", properties={"is_stock_item": 1, "is_sub_contracted_item": 1}
|
||||
).name
|
||||
rm_item = make_item(
|
||||
"Subcontracted Batch RM Item SA2",
|
||||
properties={
|
||||
"is_stock_item": 1,
|
||||
"create_new_batch": 1,
|
||||
"has_batch_no": 1,
|
||||
"batch_number_series": "BATCH-RM-IRM-.####",
|
||||
},
|
||||
).name
|
||||
|
||||
make_bom(item=fg_item, raw_materials=[rm_item], rate=100, currency="INR")
|
||||
|
||||
service_items = [
|
||||
{
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_code": service_item,
|
||||
"qty": 5,
|
||||
"rate": 100,
|
||||
"fg_item": fg_item,
|
||||
"fg_item_qty": 5,
|
||||
},
|
||||
]
|
||||
sco = get_subcontracting_order(service_items=service_items)
|
||||
rm_items = get_rm_items(sco.supplied_items)
|
||||
rm_items[0]["qty"] += 1
|
||||
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||
|
||||
for item in rm_items:
|
||||
item["sco_rm_detail"] = sco.items[0].name
|
||||
|
||||
make_stock_transfer_entry(
|
||||
sco_no=sco.name,
|
||||
rm_items=rm_items,
|
||||
itemwise_details=copy.deepcopy(itemwise_details),
|
||||
)
|
||||
|
||||
scr1 = make_subcontracting_receipt(sco.name)
|
||||
scr1.save()
|
||||
scr1.supplied_items[0].consumed_qty = 5
|
||||
scr1.submit()
|
||||
|
||||
for key, value in get_supplied_items(scr1).items():
|
||||
transferred_detais = itemwise_details.get(key)
|
||||
self.assertEqual(value.qty, 5)
|
||||
self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:5]))
|
||||
|
||||
sco.load_from_db()
|
||||
self.assertEqual(sco.supplied_items[0].consumed_qty, 5)
|
||||
doc = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items])
|
||||
doc.save()
|
||||
self.assertEqual(doc.items[0].qty, 1)
|
||||
self.assertEqual(doc.items[0].s_warehouse, "_Test Warehouse 1 - _TC")
|
||||
self.assertEqual(doc.items[0].t_warehouse, "_Test Warehouse - _TC")
|
||||
self.assertTrue(doc.items[0].batch_no)
|
||||
self.assertTrue(doc.items[0].use_serial_batch_fields)
|
||||
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
|
||||
|
||||
def test_return_non_consumed_materials(self):
|
||||
"""
|
||||
- Set backflush based on Material Transfer.
|
||||
|
||||
@@ -309,9 +309,6 @@ class JobCard(Document):
|
||||
return overlap
|
||||
|
||||
def get_time_logs(self, args, doctype, open_job_cards=None):
|
||||
if get_datetime(args.from_time) >= get_datetime(args.to_time):
|
||||
args.to_time = add_to_date(args.from_time, minutes=args.remaining_time_in_mins)
|
||||
|
||||
jc = frappe.qb.DocType("Job Card")
|
||||
jctl = frappe.qb.DocType(doctype)
|
||||
|
||||
@@ -357,10 +354,8 @@ class JobCard(Document):
|
||||
else:
|
||||
query = query.where(jc.name.isin(open_job_cards))
|
||||
|
||||
if doctype == "Job Card Time Log":
|
||||
query = query.where(jc.docstatus < 2)
|
||||
else:
|
||||
query = query.where((jc.docstatus == 0) & (jc.total_time_in_mins == 0))
|
||||
if doctype != "Job Card Time Log":
|
||||
query = query.where(jc.total_time_in_mins == 0)
|
||||
|
||||
time_logs = query.run(as_dict=True)
|
||||
|
||||
@@ -417,13 +412,7 @@ class JobCard(Document):
|
||||
def schedule_time_logs(self, row):
|
||||
row.remaining_time_in_mins = row.time_in_mins
|
||||
while row.remaining_time_in_mins > 0:
|
||||
args = frappe._dict(
|
||||
{
|
||||
"from_time": row.planned_start_time,
|
||||
"to_time": row.planned_end_time,
|
||||
"remaining_time_in_mins": row.remaining_time_in_mins,
|
||||
}
|
||||
)
|
||||
args = frappe._dict({"from_time": row.planned_start_time, "to_time": row.planned_end_time})
|
||||
|
||||
self.validate_overlap_for_workstation(args, row)
|
||||
self.check_workstation_time(row)
|
||||
|
||||
@@ -50,11 +50,7 @@ def get_returned_materials(work_orders):
|
||||
|
||||
raw_materials = frappe.get_all(
|
||||
"Stock Entry",
|
||||
fields=[
|
||||
"`tabStock Entry`.`work_order`",
|
||||
"`tabStock Entry Detail`.`item_code`",
|
||||
"`tabStock Entry Detail`.`qty`",
|
||||
],
|
||||
fields=["`tabStock Entry Detail`.`item_code`", "`tabStock Entry Detail`.`qty`"],
|
||||
filters=[
|
||||
["Stock Entry", "is_return", "=", 1],
|
||||
["Stock Entry Detail", "docstatus", "=", 1],
|
||||
@@ -63,14 +59,12 @@ def get_returned_materials(work_orders):
|
||||
)
|
||||
|
||||
for d in raw_materials:
|
||||
key = (d.work_order, d.item_code)
|
||||
raw_materials_qty[key] += d.qty
|
||||
raw_materials_qty[d.item_code] += d.qty
|
||||
|
||||
for row in work_orders:
|
||||
row.returned_qty = 0.0
|
||||
key = (row.parent, row.raw_material_item_code)
|
||||
if raw_materials_qty.get(key):
|
||||
row.returned_qty = raw_materials_qty.get(key)
|
||||
if raw_materials_qty.get(row.raw_material_item_code):
|
||||
row.returned_qty = raw_materials_qty.get(row.raw_material_item_code)
|
||||
|
||||
|
||||
def get_fields():
|
||||
|
||||
@@ -386,4 +386,3 @@ erpnext.patches.v14_0.update_stock_uom_in_work_order_item
|
||||
erpnext.patches.v15_0.set_is_exchange_gain_loss_in_payment_entry_deductions
|
||||
erpnext.patches.v15_0.enable_allow_existing_serial_no
|
||||
erpnext.patches.v15_0.update_cc_in_process_statement_of_accounts
|
||||
erpnext.patches.v15_0.update_asset_status_to_work_in_progress
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
Asset = frappe.qb.DocType("Asset")
|
||||
query = (
|
||||
frappe.qb.update(Asset)
|
||||
.set(Asset.status, "Work In Progress")
|
||||
.where((Asset.docstatus == 0) & (Asset.is_composite_asset == 1))
|
||||
)
|
||||
query.run()
|
||||
@@ -147,32 +147,29 @@ frappe.ui.form.on("Project", {
|
||||
set_project_status_button: function (frm) {
|
||||
frm.add_custom_button(
|
||||
__("Set Project Status"),
|
||||
() => frm.events.get_project_status_dialog(frm).show(),
|
||||
() => {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __("Set Project Status"),
|
||||
fields: [
|
||||
{
|
||||
fieldname: "status",
|
||||
fieldtype: "Select",
|
||||
label: "Status",
|
||||
reqd: 1,
|
||||
options: "Completed\nCancelled",
|
||||
},
|
||||
],
|
||||
primary_action: function () {
|
||||
frm.events.set_status(frm, d.get_values().status);
|
||||
d.hide();
|
||||
},
|
||||
primary_action_label: __("Set Project Status"),
|
||||
}).show();
|
||||
},
|
||||
__("Actions")
|
||||
);
|
||||
},
|
||||
|
||||
get_project_status_dialog: function (frm) {
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __("Set Project Status"),
|
||||
fields: [
|
||||
{
|
||||
fieldname: "status",
|
||||
fieldtype: "Select",
|
||||
label: "Status",
|
||||
reqd: 1,
|
||||
options: "Completed\nCancelled",
|
||||
},
|
||||
],
|
||||
primary_action: function () {
|
||||
frm.events.set_status(frm, dialog.get_values().status);
|
||||
dialog.hide();
|
||||
},
|
||||
primary_action_label: __("Set Project Status"),
|
||||
});
|
||||
return dialog;
|
||||
},
|
||||
|
||||
create_duplicate: function (frm) {
|
||||
return new Promise((resolve) => {
|
||||
frappe.prompt("Project Name", (data) => {
|
||||
@@ -191,7 +188,7 @@ frappe.ui.form.on("Project", {
|
||||
},
|
||||
|
||||
set_status: function (frm, status) {
|
||||
frappe.confirm(__("Set Project and all Tasks to status {0}?", [__(status).bold()]), () => {
|
||||
frappe.confirm(__("Set Project and all Tasks to status {0}?", [status.bold()]), () => {
|
||||
frappe
|
||||
.xcall("erpnext.projects.doctype.project.project.set_project_status", {
|
||||
project: frm.doc.name,
|
||||
|
||||
@@ -16,7 +16,7 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager {
|
||||
}
|
||||
|
||||
make_dt() {
|
||||
const me = this;
|
||||
var me = this;
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_bank_transactions",
|
||||
args: {
|
||||
@@ -193,7 +193,6 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager {
|
||||
args: {
|
||||
bank_account: this.bank_account,
|
||||
till_date: this.bank_statement_to_date,
|
||||
company: this.company,
|
||||
},
|
||||
callback: (response) => (this.cleared_balance = response.message),
|
||||
});
|
||||
|
||||
@@ -302,16 +302,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
return;
|
||||
}
|
||||
|
||||
let show_qc_button = true;
|
||||
if (["Sales Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype)) {
|
||||
show_qc_button = this.frm.doc.update_stock;
|
||||
}
|
||||
|
||||
const me = this;
|
||||
if (!this.frm.is_new() && this.frm.doc.docstatus === 0 && frappe.model.can_create("Quality Inspection") && show_qc_button) {
|
||||
if (!this.frm.is_new() && this.frm.doc.docstatus === 0 && frappe.model.can_create("Quality Inspection")) {
|
||||
this.frm.add_custom_button(__("Quality Inspection(s)"), () => {
|
||||
me.make_quality_inspection();
|
||||
}, __("Create"));
|
||||
this.frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||
}
|
||||
|
||||
const inspection_type = ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"].includes(this.frm.doc.doctype)
|
||||
@@ -978,6 +974,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
transaction_date() {
|
||||
this.apply_pricing_rule()
|
||||
if (this.frm.doc.transaction_date) {
|
||||
this.frm.transaction_date = this.frm.doc.transaction_date;
|
||||
frappe.ui.form.trigger(this.frm.doc.doctype, "currency");
|
||||
@@ -986,6 +983,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
posting_date() {
|
||||
var me = this;
|
||||
me.apply_pricing_rule()
|
||||
if (this.frm.doc.posting_date) {
|
||||
this.frm.posting_date = this.frm.doc.posting_date;
|
||||
|
||||
|
||||
@@ -210,21 +210,10 @@ erpnext.PointOfSale.ItemDetails = class {
|
||||
|
||||
make_auto_serial_selection_btn(item) {
|
||||
if (item.has_serial_no || item.has_batch_no) {
|
||||
if (item.has_serial_no && item.has_batch_no) {
|
||||
this.$form_container.append(
|
||||
`<div class="btn btn-sm btn-secondary auto-fetch-btn" style="margin-top: 6px">${__(
|
||||
"Select Serial No / Batch No"
|
||||
)}</div>`
|
||||
);
|
||||
} else {
|
||||
const classname = item.has_serial_no ? ".serial_no-control" : ".batch_no-control";
|
||||
const label = item.has_serial_no ? __("Select Serial No") : __("Select Batch No");
|
||||
this.$form_container
|
||||
.find(classname)
|
||||
.append(
|
||||
`<div class="btn btn-sm btn-secondary auto-fetch-btn" style="margin-top: 6px">${label}</div>`
|
||||
);
|
||||
}
|
||||
const label = item.has_serial_no ? __("Select Serial No") : __("Select Batch No");
|
||||
this.$form_container.append(
|
||||
`<div class="btn btn-sm btn-secondary auto-fetch-btn">${label}</div>`
|
||||
);
|
||||
this.$form_container.find(".serial_no-control").find("textarea").css("height", "6rem");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ from frappe.utils import cint, flt
|
||||
|
||||
from erpnext.controllers.accounts_controller import get_taxes_and_charges, merge_taxes
|
||||
from erpnext.controllers.selling_controller import SellingController
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no
|
||||
|
||||
form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
|
||||
|
||||
@@ -979,6 +980,11 @@ def make_sales_invoice(source_name, target_doc=None, args=None):
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.qty = to_make_invoice_qty_map[source_doc.name]
|
||||
|
||||
if source_doc.serial_no and source_parent.per_billed > 0 and not source_parent.is_return:
|
||||
target_doc.serial_no = get_delivery_note_serial_no(
|
||||
source_doc.item_code, target_doc.qty, source_parent.name
|
||||
)
|
||||
|
||||
def get_pending_qty(item_row):
|
||||
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
|
||||
|
||||
|
||||
@@ -1557,7 +1557,7 @@ def get_type_of_transaction(parent_doc, child_row):
|
||||
elif parent_doc.get("doctype") == "Stock Reconciliation":
|
||||
type_of_transaction = "Inward"
|
||||
|
||||
if parent_doc.get("is_return") and parent_doc.get("doctype") != "Stock Entry":
|
||||
if parent_doc.get("is_return"):
|
||||
type_of_transaction = "Inward"
|
||||
if (
|
||||
parent_doc.get("doctype") in ["Purchase Receipt", "Purchase Invoice"]
|
||||
|
||||
@@ -169,6 +169,21 @@ def update_maintenance_status():
|
||||
frappe.db.set_value("Serial No", doc.name, "maintenance_status", doc.maintenance_status)
|
||||
|
||||
|
||||
def get_delivery_note_serial_no(item_code, qty, delivery_note):
|
||||
serial_nos = ""
|
||||
dn_serial_nos = frappe.db.sql_list(
|
||||
f""" select name from `tabSerial No`
|
||||
where item_code = %(item_code)s and delivery_document_no = %(delivery_note)s
|
||||
and sales_invoice is null limit {cint(qty)}""",
|
||||
{"item_code": item_code, "delivery_note": delivery_note},
|
||||
)
|
||||
|
||||
if dn_serial_nos and len(dn_serial_nos) > 0:
|
||||
serial_nos = "\n".join(dn_serial_nos)
|
||||
|
||||
return serial_nos
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def auto_fetch_serial_number(
|
||||
qty: int,
|
||||
|
||||
@@ -483,7 +483,7 @@ class StockEntry(StockController):
|
||||
if self.process_loss_qty:
|
||||
total += flt(self.process_loss_qty, precision)
|
||||
|
||||
if self.fg_completed_qty != flt(total, precision):
|
||||
if self.fg_completed_qty != total:
|
||||
frappe.throw(
|
||||
_(
|
||||
"The finished product {0} quantity {1} and For Quantity {2} cannot be different"
|
||||
@@ -610,9 +610,7 @@ class StockEntry(StockController):
|
||||
completed_qty = (
|
||||
d.completed_qty + d.process_loss_qty + (allowance_percentage / 100 * d.completed_qty)
|
||||
)
|
||||
if flt(total_completed_qty, self.precision("fg_completed_qty")) > flt(
|
||||
completed_qty, self.precision("fg_completed_qty")
|
||||
):
|
||||
if total_completed_qty > flt(completed_qty):
|
||||
job_card = frappe.db.get_value("Job Card", {"operation_id": d.name}, "name")
|
||||
if not job_card:
|
||||
frappe.throw(
|
||||
|
||||
@@ -317,6 +317,7 @@ class StockBalanceReport:
|
||||
.where((sle.docstatus < 2) & (sle.is_cancelled == 0))
|
||||
.orderby(sle.posting_datetime)
|
||||
.orderby(sle.creation)
|
||||
.orderby(sle.actual_qty)
|
||||
)
|
||||
|
||||
query = self.apply_inventory_dimensions_filters(query, sle)
|
||||
|
||||
@@ -1039,7 +1039,7 @@ class update_entries_after:
|
||||
|
||||
def get_dynamic_incoming_outgoing_rate(self, sle):
|
||||
# Get updated incoming/outgoing rate from transaction
|
||||
if sle.recalculate_rate or self.has_landed_cost_based_on_pi(sle):
|
||||
if sle.recalculate_rate:
|
||||
rate = self.get_incoming_outgoing_rate_from_transaction(sle)
|
||||
|
||||
if flt(sle.actual_qty) >= 0:
|
||||
@@ -1047,14 +1047,6 @@ class update_entries_after:
|
||||
else:
|
||||
sle.outgoing_rate = rate
|
||||
|
||||
def has_landed_cost_based_on_pi(self, sle):
|
||||
if sle.voucher_type == "Purchase Receipt" and frappe.db.get_single_value(
|
||||
"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate"
|
||||
):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_incoming_outgoing_rate_from_transaction(self, sle):
|
||||
rate = 0
|
||||
# Material Transfer, Repack, Manufacturing
|
||||
|
||||
Reference in New Issue
Block a user