Compare commits

..

35 Commits

Author SHA1 Message Date
Ankush Menat
d3117cca0c perf: add indexes on payment entry reference
Adds index on:
1. reference doctype
2. reference name

*Why not composite index?*

There are three type of queries on this doctype

- filtering ref_doctype - doctype index helps here
- filtering ref_name - name index helps here
- filtering both - name index helps here too. Since it has sufficiently
  high cardinality. Composite index wont help in case where ref_doctype
  isn't specfied.
2022-12-12 12:32:14 +05:30
rohitwaghchaure
7cbb6c4de9 Merge pull request #33236 from rohitwaghchaure/feat-warehouse-wise-stock-balance
feat: warehouse wise stock balance
2022-12-06 17:58:31 +05:30
Deepesh Garg
b602a0dcb0 Merge pull request #33146 from barredterra/reload-currency-exchange-settings
fix: reload Currency Exchange Settings in patch
2022-12-06 17:38:13 +05:30
Deepesh Garg
26adbc6282 Merge pull request #33192 from deepeshgarg007/bundle_item_rates
fix: Bundle item rates
2022-12-06 17:37:22 +05:30
Rohit Waghchaure
861aa9e08a feat: warehouse wise stock balance 2022-12-06 17:33:33 +05:30
Deepesh Garg
c1a82dc9e5 Merge pull request #33191 from ruthra-kumar/key_error_in_profit_loss_report
fix: key error while filtering on date range and reporting on foreign currency
2022-12-06 15:42:53 +05:30
Deepesh Garg
b1242bc56c chore: Update tests 2022-12-06 15:14:48 +05:30
Deepesh Garg
af1f98e188 Merge branch 'develop' of https://github.com/frappe/erpnext into bundle_item_rates 2022-12-06 13:59:51 +05:30
Deepesh Garg
e5566b31d5 chore: Consider bundle qty as well 2022-12-06 13:59:45 +05:30
ruthra kumar
a6794c3606 fix: key error on p/l and balance sheet reports on foreign currency 2022-12-06 13:48:46 +05:30
ruthra kumar
19db7e2989 fix: replace sql code with fields list in get_cached_value 2022-12-06 13:48:46 +05:30
Ankush Menat
d23b5d8f2f ci: use mariadb 10.6 (#33220)
https://github.com/frappe/frappe/pull/19116

[skip ci]
2022-12-06 12:58:07 +05:30
Deepesh Garg
01b84a9751 Merge pull request #33194 from barredterra/validate-employee-dates
refactor: validate dates in Employee
2022-12-06 12:34:15 +05:30
Deepesh Garg
3aa6f97420 Merge pull request #33216 from barredterra/validate-accounts-dates
refactor: validate dates in accounts module
2022-12-06 12:33:01 +05:30
Deepesh Garg
63393fa503 Merge pull request #33217 from barredterra/validate-project-dates
refactor: validate dates in project and task
2022-12-06 12:32:27 +05:30
Deepesh Garg
921f8edde8 Merge pull request #33219 from deepeshgarg007/internal_transfer_editable_rate
fix: Allow item rate updates for non-stock invoices
2022-12-06 09:12:24 +05:30
Raffael Meyer
5853b80d25 Merge branch 'develop' into reload-currency-exchange-settings 2022-12-06 02:30:55 +01:00
Raffael Meyer
8301d3b13f Merge branch 'develop' into key_error_in_profit_loss_report 2022-12-06 02:08:47 +01:00
barredterra
a26a29f33b fix: incorrect dates in test records 2022-12-05 19:49:05 +01:00
Raffael Meyer
a074ffa880 Merge branch 'develop' into validate-employee-dates 2022-12-05 19:38:01 +01:00
Raffael Meyer
e526a0e282 Merge branch 'develop' into validate-accounts-dates 2022-12-05 19:37:54 +01:00
Raffael Meyer
a2abc879c9 Merge branch 'develop' into validate-project-dates 2022-12-05 19:37:46 +01:00
rohitwaghchaure
d0478ec3b8 Merge pull request #33224 from rohitwaghchaure/fixed-partial-work-order-incorrect-batch-picked
fix: non empty FG batch picked while completing work order
2022-12-05 20:29:19 +05:30
Rohit Waghchaure
713330cbf6 fix: non empty FG batch picked while completing work order 2022-12-05 18:15:13 +05:30
ruthra kumar
7bd0e977bf Merge pull request #33222 from ruthra-kumar/data_import_errors_for_sales_invoice
fix: data import mandatory account_head, charge_type
2022-12-05 16:56:43 +05:30
ruthra kumar
3814db02eb fix: data import mandatory account_head, charge_type 2022-12-05 16:24:05 +05:30
Deepesh Garg
ef9d126254 fix: Allow item rate udpates for non-stock invoices 2022-12-05 10:17:19 +05:30
barredterra
31db0e7c79 refactor: validate parent_expected_end_date in Task 2022-12-04 15:28:38 +01:00
barredterra
2c4eb371a6 refactor: validate dates in project and task 2022-12-04 15:15:07 +01:00
barredterra
eb66b749b2 refactor: validate dates in accounts module 2022-12-04 14:41:21 +01:00
Raffael Meyer
083a954b5d Merge branch 'develop' into validate-employee-dates 2022-12-04 14:30:51 +01:00
barredterra
03f7bfbbde refactor: validate dates 2022-12-01 12:42:03 +01:00
Deepesh Garg
826f45ad60 fix: Bundle item rates 2022-12-01 16:11:10 +05:30
ruthra kumar
9b8d6fe411 fix: key error while filtering on date range and different currency 2022-12-01 13:38:41 +05:30
barredterra
06e094b5fc fix: reload currency exchange settings 2022-11-28 22:58:31 +01:00
24 changed files with 361 additions and 237 deletions

View File

@@ -9,10 +9,6 @@ from frappe.model.document import Document
from frappe.utils import add_days, add_years, cstr, getdate from frappe.utils import add_days, add_years, cstr, getdate
class FiscalYearIncorrectDate(frappe.ValidationError):
pass
class FiscalYear(Document): class FiscalYear(Document):
@frappe.whitelist() @frappe.whitelist()
def set_as_default(self): def set_as_default(self):
@@ -53,23 +49,18 @@ class FiscalYear(Document):
) )
def validate_dates(self): def validate_dates(self):
self.validate_from_to_dates("year_start_date", "year_end_date")
if self.is_short_year: if self.is_short_year:
# Fiscal Year can be shorter than one year, in some jurisdictions # Fiscal Year can be shorter than one year, in some jurisdictions
# under certain circumstances. For example, in the USA and Germany. # under certain circumstances. For example, in the USA and Germany.
return return
if getdate(self.year_start_date) > getdate(self.year_end_date):
frappe.throw(
_("Fiscal Year Start Date should be one year earlier than Fiscal Year End Date"),
FiscalYearIncorrectDate,
)
date = getdate(self.year_start_date) + relativedelta(years=1) - relativedelta(days=1) date = getdate(self.year_start_date) + relativedelta(years=1) - relativedelta(days=1)
if getdate(self.year_end_date) != date: if getdate(self.year_end_date) != date:
frappe.throw( frappe.throw(
_("Fiscal Year End Date should be one year after Fiscal Year Start Date"), _("Fiscal Year End Date should be one year after Fiscal Year Start Date"),
FiscalYearIncorrectDate, frappe.exceptions.InvalidDates,
) )
def on_update(self): def on_update(self):
@@ -169,5 +160,6 @@ def auto_create_fiscal_year():
def get_from_and_to_date(fiscal_year): def get_from_and_to_date(fiscal_year):
fields = ["year_start_date as from_date", "year_end_date as to_date"] fields = ["year_start_date", "year_end_date"]
return frappe.get_cached_value("Fiscal Year", fiscal_year, fields, as_dict=1) cached_results = frappe.get_cached_value("Fiscal Year", fiscal_year, fields, as_dict=1)
return dict(from_date=cached_results.year_start_date, to_date=cached_results.year_end_date)

View File

@@ -7,8 +7,6 @@ import unittest
import frappe import frappe
from frappe.utils import now_datetime from frappe.utils import now_datetime
from erpnext.accounts.doctype.fiscal_year.fiscal_year import FiscalYearIncorrectDate
test_ignore = ["Company"] test_ignore = ["Company"]
@@ -26,7 +24,7 @@ class TestFiscalYear(unittest.TestCase):
} }
) )
self.assertRaises(FiscalYearIncorrectDate, fy.insert) self.assertRaises(frappe.exceptions.InvalidDates, fy.insert)
def test_record_generator(): def test_record_generator():
@@ -35,8 +33,8 @@ def test_record_generator():
"doctype": "Fiscal Year", "doctype": "Fiscal Year",
"year": "_Test Short Fiscal Year 2011", "year": "_Test Short Fiscal Year 2011",
"is_short_year": 1, "is_short_year": 1,
"year_end_date": "2011-04-01", "year_start_date": "2011-04-01",
"year_start_date": "2011-12-31", "year_end_date": "2011-12-31",
} }
] ]

View File

@@ -25,7 +25,8 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Type", "label": "Type",
"options": "DocType", "options": "DocType",
"reqd": 1 "reqd": 1,
"search_index": 1
}, },
{ {
"columns": 2, "columns": 2,
@@ -35,7 +36,8 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Name", "label": "Name",
"options": "reference_doctype", "options": "reference_doctype",
"reqd": 1 "reqd": 1,
"search_index": 1
}, },
{ {
"fieldname": "due_date", "fieldname": "due_date",
@@ -104,7 +106,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-09-26 17:06:55.597389", "modified": "2022-12-12 12:31:44.919895",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry Reference", "name": "Payment Entry Reference",
@@ -113,5 +115,6 @@
"quick_entry": 1, "quick_entry": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -10,7 +10,7 @@ import re
import frappe import frappe
from frappe import _, throw from frappe import _, throw
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cint, flt, getdate from frappe.utils import cint, flt
apply_on_dict = {"Item Code": "items", "Item Group": "item_groups", "Brand": "brands"} apply_on_dict = {"Item Code": "items", "Item Group": "item_groups", "Brand": "brands"}
@@ -184,8 +184,7 @@ class PricingRule(Document):
if self.is_cumulative and not (self.valid_from and self.valid_upto): if self.is_cumulative and not (self.valid_from and self.valid_upto):
frappe.throw(_("Valid from and valid upto fields are mandatory for the cumulative")) frappe.throw(_("Valid from and valid upto fields are mandatory for the cumulative"))
if self.valid_from and self.valid_upto and getdate(self.valid_from) > getdate(self.valid_upto): self.validate_from_to_dates("valid_from", "valid_upto")
frappe.throw(_("Valid from date must be less than valid upto date"))
def validate_condition(self): def validate_condition(self):
if ( if (

View File

@@ -231,7 +231,9 @@ class PurchaseInvoice(BuyingController):
) )
if ( if (
cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) and not self.is_return cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate"))
and not self.is_return
and not self.is_internal_supplier
): ):
self.validate_rate_with_reference_doc( self.validate_rate_with_reference_doc(
[ [

View File

@@ -921,6 +921,7 @@
"fieldtype": "Table", "fieldtype": "Table",
"hide_days": 1, "hide_days": 1,
"hide_seconds": 1, "hide_seconds": 1,
"label": "Sales Taxes and Charges",
"oldfieldname": "other_charges", "oldfieldname": "other_charges",
"oldfieldtype": "Table", "oldfieldtype": "Table",
"options": "Sales Taxes and Charges" "options": "Sales Taxes and Charges"
@@ -2133,7 +2134,7 @@
"link_fieldname": "consolidated_invoice" "link_fieldname": "consolidated_invoice"
} }
], ],
"modified": "2022-11-17 17:17:10.883487", "modified": "2022-12-05 16:18:14.532114",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@@ -32,7 +32,7 @@ class TaxRule(Document):
def validate(self): def validate(self):
self.validate_tax_template() self.validate_tax_template()
self.validate_date() self.validate_from_to_dates("from_date", "to_date")
self.validate_filters() self.validate_filters()
self.validate_use_for_shopping_cart() self.validate_use_for_shopping_cart()
@@ -51,10 +51,6 @@ class TaxRule(Document):
if not (self.sales_tax_template or self.purchase_tax_template): if not (self.sales_tax_template or self.purchase_tax_template):
frappe.throw(_("Tax Template is mandatory.")) frappe.throw(_("Tax Template is mandatory."))
def validate_date(self):
if self.from_date and self.to_date and self.from_date > self.to_date:
frappe.throw(_("From Date cannot be greater than To Date"))
def validate_filters(self): def validate_filters(self):
filters = { filters = {
"tax_type": self.tax_type, "tax_type": self.tax_type,

View File

@@ -28,7 +28,7 @@ def get_currency(filters):
filters["presentation_currency"] if filters.get("presentation_currency") else company_currency filters["presentation_currency"] if filters.get("presentation_currency") else company_currency
) )
report_date = filters.get("to_date") report_date = filters.get("to_date") or filters.get("period_end_date")
if not report_date: if not report_date:
fiscal_year_to_date = get_from_and_to_date(filters.get("to_fiscal_year"))["to_date"] fiscal_year_to_date = get_from_and_to_date(filters.get("to_fiscal_year"))["to_date"]

View File

@@ -322,17 +322,18 @@ class BuyingController(SubcontractingController):
) )
if self.is_internal_transfer(): if self.is_internal_transfer():
if rate != d.rate: if self.doctype == "Purchase Receipt" or self.get("update_stock"):
d.rate = rate if rate != d.rate:
frappe.msgprint( d.rate = rate
_( frappe.msgprint(
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer" _(
).format(d.idx), "Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
alert=1, ).format(d.idx),
) alert=1,
d.discount_percentage = 0.0 )
d.discount_amount = 0.0 d.discount_percentage = 0.0
d.margin_rate_or_amount = 0.0 d.discount_amount = 0.0
d.margin_rate_or_amount = 0.0
def validate_for_subcontracting(self): def validate_for_subcontracting(self):
if self.is_subcontracted and self.get("is_old_subcontracting_flow"): if self.is_subcontracted and self.get("is_old_subcontracting_flow"):

View File

@@ -442,30 +442,31 @@ class SellingController(StockController):
# For internal transfers use incoming rate as the valuation rate # For internal transfers use incoming rate as the valuation rate
if self.is_internal_transfer(): if self.is_internal_transfer():
if d.doctype == "Packed Item": if self.doctype == "Delivery Note" or self.get("update_stock"):
incoming_rate = flt( if d.doctype == "Packed Item":
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor, incoming_rate = flt(
d.precision("incoming_rate"), flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
) d.precision("incoming_rate"),
if d.incoming_rate != incoming_rate:
d.incoming_rate = incoming_rate
else:
rate = flt(
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
d.precision("rate"),
)
if d.rate != rate:
d.rate = rate
frappe.msgprint(
_(
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
).format(d.idx),
alert=1,
) )
if d.incoming_rate != incoming_rate:
d.incoming_rate = incoming_rate
else:
rate = flt(
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
d.precision("rate"),
)
if d.rate != rate:
d.rate = rate
frappe.msgprint(
_(
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
).format(d.idx),
alert=1,
)
d.discount_percentage = 0.0 d.discount_percentage = 0.0
d.discount_amount = 0.0 d.discount_amount = 0.0
d.margin_rate_or_amount = 0.0 d.margin_rate_or_amount = 0.0
elif self.get("return_against"): elif self.get("return_against"):
# Get incoming rate of return entry from reference document # Get incoming rate of return entry from reference document

View File

@@ -635,6 +635,10 @@ class TestWorkOrder(FrappeTestCase):
bom.submit() bom.submit()
bom_name = bom.name bom_name = bom.name
ste1 = test_stock_entry.make_stock_entry(
item_code=rm1, target="_Test Warehouse - _TC", qty=32, basic_rate=5000.0
)
work_order = make_wo_order_test_record( work_order = make_wo_order_test_record(
item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1 item=fg_item, skip_transfer=True, planned_start_date=now(), qty=1
) )
@@ -659,11 +663,29 @@ class TestWorkOrder(FrappeTestCase):
work_order.insert() work_order.insert()
work_order.submit() work_order.submit()
self.assertEqual(work_order.has_batch_no, 1) self.assertEqual(work_order.has_batch_no, 1)
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 30)) batches = frappe.get_all("Batch", filters={"reference_name": work_order.name})
self.assertEqual(len(batches), 3)
batches = [batch.name for batch in batches]
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 10))
for row in ste1.get("items"): for row in ste1.get("items"):
if row.is_finished_item: if row.is_finished_item:
self.assertEqual(row.item_code, fg_item) self.assertEqual(row.item_code, fg_item)
self.assertEqual(row.qty, 10) self.assertEqual(row.qty, 10)
self.assertTrue(row.batch_no in batches)
batches.remove(row.batch_no)
ste1.submit()
remaining_batches = []
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 20))
for row in ste1.get("items"):
if row.is_finished_item:
self.assertEqual(row.item_code, fg_item)
self.assertEqual(row.qty, 10)
remaining_batches.append(row.batch_no)
self.assertEqual(sorted(remaining_batches), sorted(batches))
frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 0) frappe.db.set_value("Manufacturing Settings", None, "make_serial_no_batch_from_work_order", 0)

View File

@@ -1,5 +1,8 @@
import frappe
from erpnext.setup.install import setup_currency_exchange from erpnext.setup.install import setup_currency_exchange
def execute(): def execute():
frappe.reload_doc("accounts", "doctype", "currency_exchange_settings")
setup_currency_exchange() setup_currency_exchange()

View File

@@ -42,6 +42,8 @@ class Project(Document):
self.send_welcome_email() self.send_welcome_email()
self.update_costing() self.update_costing()
self.update_percent_complete() self.update_percent_complete()
self.validate_from_to_dates("expected_start_date", "expected_end_date")
self.validate_from_to_dates("actual_start_date", "actual_end_date")
def copy_from_template(self): def copy_from_template(self):
""" """

View File

@@ -9,6 +9,7 @@ from frappe import _, throw
from frappe.desk.form.assign_to import clear, close_all_assignments from frappe.desk.form.assign_to import clear, close_all_assignments
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from frappe.utils import add_days, cstr, date_diff, flt, get_link_to_form, getdate, today from frappe.utils import add_days, cstr, date_diff, flt, get_link_to_form, getdate, today
from frappe.utils.data import format_date
from frappe.utils.nestedset import NestedSet from frappe.utils.nestedset import NestedSet
@@ -16,10 +17,6 @@ class CircularReferenceError(frappe.ValidationError):
pass pass
class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError):
pass
class Task(NestedSet): class Task(NestedSet):
nsm_parent_field = "parent_task" nsm_parent_field = "parent_task"
@@ -34,8 +31,6 @@ class Task(NestedSet):
def validate(self): def validate(self):
self.validate_dates() self.validate_dates()
self.validate_parent_expected_end_date()
self.validate_parent_project_dates()
self.validate_progress() self.validate_progress()
self.validate_status() self.validate_status()
self.update_depends_on() self.update_depends_on()
@@ -43,51 +38,42 @@ class Task(NestedSet):
self.validate_completed_on() self.validate_completed_on()
def validate_dates(self): def validate_dates(self):
if ( self.validate_from_to_dates("exp_start_date", "exp_end_date")
self.exp_start_date self.validate_from_to_dates("act_start_date", "act_end_date")
and self.exp_end_date self.validate_parent_expected_end_date()
and getdate(self.exp_start_date) > getdate(self.exp_end_date) self.validate_parent_project_dates()
):
frappe.throw(
_("{0} can not be greater than {1}").format(
frappe.bold("Expected Start Date"), frappe.bold("Expected End Date")
)
)
if (
self.act_start_date
and self.act_end_date
and getdate(self.act_start_date) > getdate(self.act_end_date)
):
frappe.throw(
_("{0} can not be greater than {1}").format(
frappe.bold("Actual Start Date"), frappe.bold("Actual End Date")
)
)
def validate_parent_expected_end_date(self): def validate_parent_expected_end_date(self):
if self.parent_task: if not self.parent_task or not self.exp_end_date:
parent_exp_end_date = frappe.db.get_value("Task", self.parent_task, "exp_end_date") return
if parent_exp_end_date and getdate(self.get("exp_end_date")) > getdate(parent_exp_end_date):
frappe.throw( parent_exp_end_date = frappe.db.get_value("Task", self.parent_task, "exp_end_date")
_( if not parent_exp_end_date:
"Expected End Date should be less than or equal to parent task's Expected End Date {0}." return
).format(getdate(parent_exp_end_date))
) if getdate(self.exp_end_date) > getdate(parent_exp_end_date):
frappe.throw(
_(
"Expected End Date should be less than or equal to parent task's Expected End Date {0}."
).format(format_date(parent_exp_end_date)),
frappe.exceptions.InvalidDates,
)
def validate_parent_project_dates(self): def validate_parent_project_dates(self):
if not self.project or frappe.flags.in_test: if not self.project or frappe.flags.in_test:
return return
expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date") if project_end_date := frappe.db.get_value("Project", self.project, "expected_end_date"):
project_end_date = getdate(project_end_date)
if expected_end_date: for fieldname in ("exp_start_date", "exp_end_date", "act_start_date", "act_end_date"):
validate_project_dates( task_date = self.get(fieldname)
getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected" if task_date and date_diff(project_end_date, getdate(task_date)) < 0:
) frappe.throw(
validate_project_dates( _("Task's {0} cannot be after Project's Expected End Date.").format(
getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual" _(self.meta.get_label(fieldname))
) ),
frappe.exceptions.InvalidDates,
)
def validate_status(self): def validate_status(self):
if self.is_template and self.status != "Template": if self.is_template and self.status != "Template":
@@ -398,15 +384,3 @@ def add_multiple_tasks(data, parent):
def on_doctype_update(): def on_doctype_update():
frappe.db.add_index("Task", ["lft", "rgt"]) frappe.db.add_index("Task", ["lft", "rgt"])
def validate_project_dates(project_end_date, task, task_start, task_end, actual_or_expected_date):
if task.get(task_start) and date_diff(project_end_date, getdate(task.get(task_start))) < 0:
frappe.throw(
_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date)
)
if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0:
frappe.throw(
_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date)
)

View File

@@ -145,33 +145,10 @@ class Employee(NestedSet):
if self.date_of_birth and getdate(self.date_of_birth) > getdate(today()): if self.date_of_birth and getdate(self.date_of_birth) > getdate(today()):
throw(_("Date of Birth cannot be greater than today.")) throw(_("Date of Birth cannot be greater than today."))
if ( self.validate_from_to_dates("date_of_birth", "date_of_joining")
self.date_of_birth self.validate_from_to_dates("date_of_joining", "date_of_retirement")
and self.date_of_joining self.validate_from_to_dates("date_of_joining", "relieving_date")
and getdate(self.date_of_birth) >= getdate(self.date_of_joining) self.validate_from_to_dates("date_of_joining", "contract_end_date")
):
throw(_("Date of Joining must be greater than Date of Birth"))
elif (
self.date_of_retirement
and self.date_of_joining
and (getdate(self.date_of_retirement) <= getdate(self.date_of_joining))
):
throw(_("Date Of Retirement must be greater than Date of Joining"))
elif (
self.relieving_date
and self.date_of_joining
and (getdate(self.relieving_date) < getdate(self.date_of_joining))
):
throw(_("Relieving Date must be greater than or equal to Date of Joining"))
elif (
self.contract_end_date
and self.date_of_joining
and (getdate(self.contract_end_date) <= getdate(self.date_of_joining))
):
throw(_("Contract End Date must be greater than Date of Joining"))
def validate_email(self): def validate_email(self):
if self.company_email: if self.company_email:

View File

@@ -48,7 +48,7 @@ def make_packing_list(doc):
update_packed_item_from_cancelled_doc(item_row, bundle_item, pi_row, doc) update_packed_item_from_cancelled_doc(item_row, bundle_item, pi_row, doc)
if set_price_from_children: # create/update bundle item wise price dict if set_price_from_children: # create/update bundle item wise price dict
update_product_bundle_rate(parent_items_price, pi_row) update_product_bundle_rate(parent_items_price, pi_row, item_row)
if parent_items_price: if parent_items_price:
set_product_bundle_rate_amount(doc, parent_items_price) # set price in bundle item set_product_bundle_rate_amount(doc, parent_items_price) # set price in bundle item
@@ -247,7 +247,7 @@ def get_cancelled_doc_packed_item_details(old_packed_items):
return prev_doc_packed_items_map return prev_doc_packed_items_map
def update_product_bundle_rate(parent_items_price, pi_row): def update_product_bundle_rate(parent_items_price, pi_row, item_row):
""" """
Update the price dict of Product Bundles based on the rates of the Items in the bundle. Update the price dict of Product Bundles based on the rates of the Items in the bundle.
@@ -259,7 +259,7 @@ def update_product_bundle_rate(parent_items_price, pi_row):
if not rate: if not rate:
parent_items_price[key] = 0.0 parent_items_price[key] = 0.0
parent_items_price[key] += flt(pi_row.rate) parent_items_price[key] += flt((pi_row.rate * pi_row.qty) / item_row.stock_qty)
def set_product_bundle_rate_amount(doc, parent_items_price): def set_product_bundle_rate_amount(doc, parent_items_price):

View File

@@ -126,8 +126,8 @@ class TestPackedItem(FrappeTestCase):
so.packed_items[1].rate = 200 so.packed_items[1].rate = 200
so.save() so.save()
self.assertEqual(so.items[0].rate, 350) self.assertEqual(so.items[0].rate, 700)
self.assertEqual(so.items[0].amount, 700) self.assertEqual(so.items[0].amount, 1400)
def test_newly_mapped_doc_packed_items(self): def test_newly_mapped_doc_packed_items(self):
"Test impact on packed items in newly mapped DN from SO." "Test impact on packed items in newly mapped DN from SO."

View File

@@ -173,7 +173,9 @@ class PurchaseReceipt(BuyingController):
) )
if ( if (
cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) and not self.is_return cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate"))
and not self.is_return
and not self.is_internal_supplier
): ):
self.validate_rate_with_reference_doc( self.validate_rate_with_reference_doc(
[["Purchase Order", "purchase_order", "purchase_order_item"]] [["Purchase Order", "purchase_order", "purchase_order_item"]]

View File

@@ -1545,6 +1545,7 @@ class StockEntry(StockController):
"reference_name": self.pro_doc.name, "reference_name": self.pro_doc.name,
"reference_doctype": self.pro_doc.doctype, "reference_doctype": self.pro_doc.doctype,
"qty_to_produce": (">", 0), "qty_to_produce": (">", 0),
"batch_qty": ("=", 0),
} }
fields = ["qty_to_produce as qty", "produced_qty", "name"] fields = ["qty_to_produce as qty", "produced_qty", "name"]
@@ -2238,14 +2239,14 @@ class StockEntry(StockController):
d.qty -= process_loss_dict[d.item_code][1] d.qty -= process_loss_dict[d.item_code][1]
def set_serial_no_batch_for_finished_good(self): def set_serial_no_batch_for_finished_good(self):
args = {} serial_nos = ""
if self.pro_doc.serial_no: if self.pro_doc.serial_no:
self.get_serial_nos_for_fg(args) serial_nos = self.get_serial_nos_for_fg()
for row in self.items: for row in self.items:
if row.is_finished_item and row.item_code == self.pro_doc.production_item: if row.is_finished_item and row.item_code == self.pro_doc.production_item:
if args.get("serial_no"): if serial_nos:
row.serial_no = "\n".join(args["serial_no"][0 : cint(row.qty)]) row.serial_no = "\n".join(serial_nos[0 : cint(row.qty)])
def get_serial_nos_for_fg(self, args): def get_serial_nos_for_fg(self, args):
fields = [ fields = [
@@ -2258,14 +2259,14 @@ class StockEntry(StockController):
filters = [ filters = [
["Stock Entry", "work_order", "=", self.work_order], ["Stock Entry", "work_order", "=", self.work_order],
["Stock Entry", "purpose", "=", "Manufacture"], ["Stock Entry", "purpose", "=", "Manufacture"],
["Stock Entry", "docstatus", "=", 1], ["Stock Entry", "docstatus", "<", 2],
["Stock Entry Detail", "item_code", "=", self.pro_doc.production_item], ["Stock Entry Detail", "item_code", "=", self.pro_doc.production_item],
] ]
stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters) stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters)
if self.pro_doc.serial_no: if self.pro_doc.serial_no:
args["serial_no"] = self.get_available_serial_nos(stock_entries) return self.get_available_serial_nos(stock_entries)
def get_available_serial_nos(self, stock_entries): def get_available_serial_nos(self, stock_entries):
used_serial_nos = [] used_serial_nos = []

View File

@@ -0,0 +1,20 @@
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Warehouse Wise Stock Balance"] = {
"filters": [
{
"fieldname":"company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"reqd": 1,
"default": frappe.defaults.get_user_default("Company")
}
],
"initial_depth": 3,
"tree": true,
"parent_field": "parent_warehouse",
"name_field": "warehouse"
};

View File

@@ -0,0 +1,30 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2022-12-06 14:15:31.924345",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"json": "{}",
"modified": "2022-12-06 14:16:55.969214",
"modified_by": "Administrator",
"module": "Stock",
"name": "Warehouse Wise Stock Balance",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Stock Ledger Entry",
"report_name": "Warehouse Wise Stock Balance",
"report_type": "Script Report",
"roles": [
{
"role": "Stock User"
},
{
"role": "Accounts Manager"
}
]
}

View File

@@ -0,0 +1,89 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from typing import Any, Dict, List, Optional, TypedDict
import frappe
from frappe import _
from frappe.query_builder.functions import Sum
class StockBalanceFilter(TypedDict):
company: Optional[str]
warehouse: Optional[str]
SLEntry = Dict[str, Any]
def execute(filters=None):
columns, data = [], []
columns = get_columns()
data = get_data(filters)
return columns, data
def get_warehouse_wise_balance(filters: StockBalanceFilter) -> List[SLEntry]:
sle = frappe.qb.DocType("Stock Ledger Entry")
query = (
frappe.qb.from_(sle)
.select(sle.warehouse, Sum(sle.stock_value_difference).as_("stock_balance"))
.where((sle.docstatus < 2) & (sle.is_cancelled == 0))
.groupby(sle.warehouse)
)
if filters.get("company"):
query = query.where(sle.company == filters.get("company"))
data = query.run(as_list=True)
return frappe._dict(data) if data else frappe._dict()
def get_warehouses(report_filters: StockBalanceFilter):
return frappe.get_all(
"Warehouse",
fields=["name", "parent_warehouse", "is_group"],
filters={"company": report_filters.company, "disabled": 0},
order_by="lft",
)
def get_data(filters: StockBalanceFilter):
warehouse_balance = get_warehouse_wise_balance(filters)
warehouses = get_warehouses(filters)
for warehouse in warehouses:
warehouse["stock_balance"] = warehouse_balance.get(warehouse.name, 0)
update_indent(warehouses)
return warehouses
def update_indent(warehouses):
for warehouse in warehouses:
def add_indent(warehouse, indent):
warehouse.indent = indent
for child in warehouses:
if child.parent_warehouse == warehouse.name:
warehouse.stock_balance += child.stock_balance
add_indent(child, indent + 1)
if warehouse.is_group:
add_indent(warehouse, warehouse.indent or 0)
def get_columns():
return [
{
"label": _("Warehouse"),
"fieldname": "name",
"fieldtype": "Link",
"options": "Warehouse",
"width": 200,
},
{"label": _("Stock Balance"), "fieldname": "stock_balance", "fieldtype": "Float", "width": 150},
]

View File

@@ -5,7 +5,7 @@
"label": "Warehouse wise Stock Value" "label": "Warehouse wise Stock Value"
} }
], ],
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Stock\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Warehouse wise Stock Value\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Receipt\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Note\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Masters & Reports</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Transactions\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Serial No and Batch\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]", "content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Stock\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Warehouse wise Stock Value\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Receipt\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Delivery Note\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Masters &amp; Reports</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Transactions\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Serial No and Batch\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"creation": "2020-03-02 15:43:10.096528", "creation": "2020-03-02 15:43:10.096528",
"docstatus": 0, "docstatus": 0,
"doctype": "Workspace", "doctype": "Workspace",
@@ -207,80 +207,6 @@
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{
"hidden": 0,
"is_query_report": 0,
"label": "Stock Reports",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Ledger",
"link_count": 0,
"link_to": "Stock Ledger",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Balance",
"link_count": 0,
"link_to": "Stock Balance",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Projected Qty",
"link_count": 0,
"link_to": "Stock Projected Qty",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 0,
"label": "Stock Summary",
"link_count": 0,
"link_to": "stock-balance",
"link_type": "Page",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Ageing",
"link_count": 0,
"link_to": "Stock Ageing",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Item Price Stock",
"link_count": 0,
"link_to": "Item Price Stock",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{ {
"hidden": 0, "hidden": 0,
"is_query_report": 0, "is_query_report": 0,
@@ -705,15 +631,100 @@
"link_type": "Report", "link_type": "Report",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Stock Reports",
"link_count": 7,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Ledger",
"link_count": 0,
"link_to": "Stock Ledger",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Balance",
"link_count": 0,
"link_to": "Stock Balance",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Projected Qty",
"link_count": 0,
"link_to": "Stock Projected Qty",
"link_type": "Report",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 0,
"label": "Stock Summary",
"link_count": 0,
"link_to": "stock-balance",
"link_type": "Page",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Stock Ageing",
"link_count": 0,
"link_to": "Stock Ageing",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Item Price Stock",
"link_count": 0,
"link_to": "Item Price Stock",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Warehouse Wise Stock Balance",
"link_count": 0,
"link_to": "Warehouse Wise Stock Balance",
"link_type": "Report",
"onboard": 0,
"type": "Link"
} }
], ],
"modified": "2022-01-13 17:47:38.339931", "modified": "2022-12-06 17:03:56.397272",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock", "name": "Stock",
"owner": "Administrator", "owner": "Administrator",
"parent_page": "", "parent_page": "",
"public": 1, "public": 1,
"quick_lists": [],
"restrict_to_domain": "", "restrict_to_domain": "",
"roles": [], "roles": [],
"sequence_id": 24.0, "sequence_id": 24.0,