mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-08 15:42:52 +00:00
Merge remote-tracking branch 'origin/develop' into feat/so-po-advance-payment-status
This commit is contained in:
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
@@ -21,6 +21,6 @@ jobs:
|
|||||||
- name: Run backport
|
- name: Run backport
|
||||||
uses: ./actions/backport
|
uses: ./actions/backport
|
||||||
with:
|
with:
|
||||||
token: ${{secrets.BACKPORT_BOT_TOKEN}}
|
token: ${{secrets.RELEASE_TOKEN}}
|
||||||
labelsToAdd: "backport"
|
labelsToAdd: "backport"
|
||||||
title: "{{originalTitle}}"
|
title: "{{originalTitle}}"
|
||||||
|
|||||||
2
.github/workflows/initiate_release.yml
vendored
2
.github/workflows/initiate_release.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
version: ["13", "14", "15"]
|
version: ["14", "15"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: octokit/request-action@v2.x
|
- uses: octokit/request-action@v2.x
|
||||||
|
|||||||
21
.github/workflows/lock.yml
vendored
Normal file
21
.github/workflows/lock.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: 'Lock threads'
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lock:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: dessant/lock-threads@v5
|
||||||
|
with:
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
issue-inactive-days: 14
|
||||||
|
pr-inactive-days: 14
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ frappe.treeview_settings["Account"] = {
|
|||||||
|
|
||||||
// show Dr if positive since balance is calculated as debit - credit else show Cr
|
// show Dr if positive since balance is calculated as debit - credit else show Cr
|
||||||
const balance = account.balance_in_account_currency || account.balance;
|
const balance = account.balance_in_account_currency || account.balance;
|
||||||
const dr_or_cr = balance > 0 ? "Dr": "Cr";
|
const dr_or_cr = balance > 0 ? __("Dr"): __("Cr");
|
||||||
const format = (value, currency) => format_currency(Math.abs(value), currency);
|
const format = (value, currency) => format_currency(Math.abs(value), currency);
|
||||||
|
|
||||||
if (account.balance!==undefined) {
|
if (account.balance!==undefined) {
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ def create_charts(
|
|||||||
# after all accounts are already inserted.
|
# after all accounts are already inserted.
|
||||||
frappe.local.flags.ignore_update_nsm = True
|
frappe.local.flags.ignore_update_nsm = True
|
||||||
_import_accounts(chart, None, None, root_account=True)
|
_import_accounts(chart, None, None, root_account=True)
|
||||||
rebuild_tree("Account", "parent_account")
|
rebuild_tree("Account")
|
||||||
frappe.local.flags.ignore_update_nsm = False
|
frappe.local.flags.ignore_update_nsm = False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"ignore_user_permissions": 1,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"options": "Company",
|
"options": "Company",
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-01 12:32:34.044911",
|
"modified": "2024-01-03 11:13:02.669632",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Allowed To Transact With",
|
"name": "Allowed To Transact With",
|
||||||
@@ -28,5 +29,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
|
||||||
}
|
}
|
||||||
@@ -137,7 +137,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
|
|||||||
"erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
|
"erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
|
||||||
args: {
|
args: {
|
||||||
bank_account: frm.doc.bank_account,
|
bank_account: frm.doc.bank_account,
|
||||||
till_date: frm.doc.bank_statement_from_date,
|
till_date: frappe.datetime.add_days(frm.doc.bank_statement_from_date, -1)
|
||||||
},
|
},
|
||||||
callback: (response) => {
|
callback: (response) => {
|
||||||
frm.set_value("account_opening_balance", response.message);
|
frm.set_value("account_opening_balance", response.message);
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on("Bisect Accounting Statements", {
|
||||||
|
onload(frm) {
|
||||||
|
frm.trigger("render_heatmap");
|
||||||
|
},
|
||||||
|
refresh(frm) {
|
||||||
|
frm.add_custom_button(__('Bisect Left'), () => {
|
||||||
|
frm.trigger("bisect_left");
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.add_custom_button(__('Bisect Right'), () => {
|
||||||
|
frm.trigger("bisect_right");
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.add_custom_button(__('Up'), () => {
|
||||||
|
frm.trigger("move_up");
|
||||||
|
});
|
||||||
|
frm.add_custom_button(__('Build Tree'), () => {
|
||||||
|
frm.trigger("build_tree");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
render_heatmap(frm) {
|
||||||
|
let bisect_heatmap = frm.get_field("bisect_heatmap").$wrapper;
|
||||||
|
bisect_heatmap.addClass("bisect_heatmap_location");
|
||||||
|
|
||||||
|
// milliseconds in a day
|
||||||
|
let msiad=24*60*60*1000;
|
||||||
|
let datapoints = {};
|
||||||
|
let fr_dt = new Date(frm.doc.from_date).getTime();
|
||||||
|
let to_dt = new Date(frm.doc.to_date).getTime();
|
||||||
|
let bisect_start = new Date(frm.doc.current_from_date).getTime();
|
||||||
|
let bisect_end = new Date(frm.doc.current_to_date).getTime();
|
||||||
|
|
||||||
|
for(let x=fr_dt; x <= to_dt; x+=msiad){
|
||||||
|
let epoch_in_seconds = x/1000;
|
||||||
|
if ((bisect_start <= x) && (x <= bisect_end )) {
|
||||||
|
datapoints[epoch_in_seconds] = 1.0;
|
||||||
|
} else {
|
||||||
|
datapoints[epoch_in_seconds] = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new frappe.Chart(".bisect_heatmap_location", {
|
||||||
|
type: "heatmap",
|
||||||
|
data: {
|
||||||
|
dataPoints: datapoints,
|
||||||
|
start: new Date(frm.doc.from_date),
|
||||||
|
end: new Date(frm.doc.to_date),
|
||||||
|
},
|
||||||
|
countLabel: 'Bisecting',
|
||||||
|
discreteDomains: 1,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
bisect_left(frm) {
|
||||||
|
frm.call({
|
||||||
|
doc: frm.doc,
|
||||||
|
method: 'bisect_left',
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __("Bisecting Left ..."),
|
||||||
|
callback: (r) => {
|
||||||
|
frm.trigger("render_heatmap");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
bisect_right(frm) {
|
||||||
|
frm.call({
|
||||||
|
doc: frm.doc,
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __("Bisecting Right ..."),
|
||||||
|
method: 'bisect_right',
|
||||||
|
callback: (r) => {
|
||||||
|
frm.trigger("render_heatmap");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
move_up(frm) {
|
||||||
|
frm.call({
|
||||||
|
doc: frm.doc,
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __("Moving up in tree ..."),
|
||||||
|
method: 'move_up',
|
||||||
|
callback: (r) => {
|
||||||
|
frm.trigger("render_heatmap");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
build_tree(frm) {
|
||||||
|
frm.call({
|
||||||
|
doc: frm.doc,
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __("Rebuilding BTree for period ..."),
|
||||||
|
method: 'build_tree',
|
||||||
|
callback: (r) => {
|
||||||
|
frm.trigger("render_heatmap");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"creation": "2023-09-15 21:28:28.054773",
|
||||||
|
"default_view": "List",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"section_break_cvfg",
|
||||||
|
"company",
|
||||||
|
"column_break_hcam",
|
||||||
|
"from_date",
|
||||||
|
"column_break_qxbi",
|
||||||
|
"to_date",
|
||||||
|
"column_break_iwny",
|
||||||
|
"algorithm",
|
||||||
|
"section_break_8ph9",
|
||||||
|
"current_node",
|
||||||
|
"section_break_ngid",
|
||||||
|
"bisect_heatmap",
|
||||||
|
"section_break_hmsy",
|
||||||
|
"bisecting_from",
|
||||||
|
"current_from_date",
|
||||||
|
"column_break_uqyd",
|
||||||
|
"bisecting_to",
|
||||||
|
"current_to_date",
|
||||||
|
"section_break_hbyo",
|
||||||
|
"heading_cppb",
|
||||||
|
"p_l_summary",
|
||||||
|
"column_break_aivo",
|
||||||
|
"balance_sheet_summary",
|
||||||
|
"b_s_summary",
|
||||||
|
"column_break_gvwx",
|
||||||
|
"difference_heading",
|
||||||
|
"difference"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_qxbi",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "from_date",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"label": "From Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "to_date",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"label": "To Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "BFS",
|
||||||
|
"fieldname": "algorithm",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Algorithm",
|
||||||
|
"options": "BFS\nDFS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_iwny",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "current_node",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Current Node",
|
||||||
|
"options": "Bisect Nodes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_hmsy",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "current_from_date",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "current_to_date",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_uqyd",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_hbyo",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "p_l_summary",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "b_s_summary",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "difference",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_aivo",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_gvwx",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_hcam",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_ngid",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_8ph9",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"hidden": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "bisect_heatmap",
|
||||||
|
"fieldtype": "HTML",
|
||||||
|
"label": "Heatmap"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "heading_cppb",
|
||||||
|
"fieldtype": "Heading",
|
||||||
|
"label": "Profit and Loss Summary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "balance_sheet_summary",
|
||||||
|
"fieldtype": "Heading",
|
||||||
|
"label": "Balance Sheet Summary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "difference_heading",
|
||||||
|
"fieldtype": "Heading",
|
||||||
|
"label": "Difference"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "bisecting_from",
|
||||||
|
"fieldtype": "Heading",
|
||||||
|
"label": "Bisecting From"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "bisecting_to",
|
||||||
|
"fieldtype": "Heading",
|
||||||
|
"label": "Bisecting To"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_cvfg",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hide_toolbar": 1,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"issingle": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2023-12-01 16:49:54.073890",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Bisect Accounting Statements",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"role": "Administrator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"read_only": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
from collections import deque
|
||||||
|
from math import floor
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
from frappe import _
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import getdate
|
||||||
|
from frappe.utils.data import guess_date_format
|
||||||
|
|
||||||
|
|
||||||
|
class BisectAccountingStatements(Document):
|
||||||
|
# begin: auto-generated types
|
||||||
|
# This code is auto-generated. Do not modify anything in this block.
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from frappe.types import DF
|
||||||
|
|
||||||
|
algorithm: DF.Literal["BFS", "DFS"]
|
||||||
|
b_s_summary: DF.Float
|
||||||
|
company: DF.Link | None
|
||||||
|
current_from_date: DF.Datetime | None
|
||||||
|
current_node: DF.Link | None
|
||||||
|
current_to_date: DF.Datetime | None
|
||||||
|
difference: DF.Float
|
||||||
|
from_date: DF.Datetime | None
|
||||||
|
p_l_summary: DF.Float
|
||||||
|
to_date: DF.Datetime | None
|
||||||
|
# end: auto-generated types
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
self.validate_dates()
|
||||||
|
|
||||||
|
def validate_dates(self):
|
||||||
|
if getdate(self.from_date) > getdate(self.to_date):
|
||||||
|
frappe.throw(
|
||||||
|
_("From Date: {0} cannot be greater than To date: {1}").format(
|
||||||
|
frappe.bold(self.from_date), frappe.bold(self.to_date)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def bfs(self, from_date: datetime, to_date: datetime):
|
||||||
|
# Make Root node
|
||||||
|
node = frappe.new_doc("Bisect Nodes")
|
||||||
|
node.root = None
|
||||||
|
node.period_from_date = from_date
|
||||||
|
node.period_to_date = to_date
|
||||||
|
node.insert()
|
||||||
|
|
||||||
|
period_queue = deque([node])
|
||||||
|
while period_queue:
|
||||||
|
cur_node = period_queue.popleft()
|
||||||
|
delta = cur_node.period_to_date - cur_node.period_from_date
|
||||||
|
if delta.days == 0:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
cur_floor = floor(delta.days / 2)
|
||||||
|
next_to_date = cur_node.period_from_date + relativedelta(days=+cur_floor)
|
||||||
|
left_node = frappe.new_doc("Bisect Nodes")
|
||||||
|
left_node.period_from_date = cur_node.period_from_date
|
||||||
|
left_node.period_to_date = next_to_date
|
||||||
|
left_node.root = cur_node.name
|
||||||
|
left_node.generated = False
|
||||||
|
left_node.insert()
|
||||||
|
cur_node.left_child = left_node.name
|
||||||
|
period_queue.append(left_node)
|
||||||
|
|
||||||
|
next_from_date = cur_node.period_from_date + relativedelta(days=+(cur_floor + 1))
|
||||||
|
right_node = frappe.new_doc("Bisect Nodes")
|
||||||
|
right_node.period_from_date = next_from_date
|
||||||
|
right_node.period_to_date = cur_node.period_to_date
|
||||||
|
right_node.root = cur_node.name
|
||||||
|
right_node.generated = False
|
||||||
|
right_node.insert()
|
||||||
|
cur_node.right_child = right_node.name
|
||||||
|
period_queue.append(right_node)
|
||||||
|
|
||||||
|
cur_node.save()
|
||||||
|
|
||||||
|
def dfs(self, from_date: datetime, to_date: datetime):
|
||||||
|
# Make Root node
|
||||||
|
node = frappe.new_doc("Bisect Nodes")
|
||||||
|
node.root = None
|
||||||
|
node.period_from_date = from_date
|
||||||
|
node.period_to_date = to_date
|
||||||
|
node.insert()
|
||||||
|
|
||||||
|
period_stack = [node]
|
||||||
|
while period_stack:
|
||||||
|
cur_node = period_stack.pop()
|
||||||
|
delta = cur_node.period_to_date - cur_node.period_from_date
|
||||||
|
if delta.days == 0:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
cur_floor = floor(delta.days / 2)
|
||||||
|
next_to_date = cur_node.period_from_date + relativedelta(days=+cur_floor)
|
||||||
|
left_node = frappe.new_doc("Bisect Nodes")
|
||||||
|
left_node.period_from_date = cur_node.period_from_date
|
||||||
|
left_node.period_to_date = next_to_date
|
||||||
|
left_node.root = cur_node.name
|
||||||
|
left_node.generated = False
|
||||||
|
left_node.insert()
|
||||||
|
cur_node.left_child = left_node.name
|
||||||
|
period_stack.append(left_node)
|
||||||
|
|
||||||
|
next_from_date = cur_node.period_from_date + relativedelta(days=+(cur_floor + 1))
|
||||||
|
right_node = frappe.new_doc("Bisect Nodes")
|
||||||
|
right_node.period_from_date = next_from_date
|
||||||
|
right_node.period_to_date = cur_node.period_to_date
|
||||||
|
right_node.root = cur_node.name
|
||||||
|
right_node.generated = False
|
||||||
|
right_node.insert()
|
||||||
|
cur_node.right_child = right_node.name
|
||||||
|
period_stack.append(right_node)
|
||||||
|
|
||||||
|
cur_node.save()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def build_tree(self):
|
||||||
|
frappe.db.delete("Bisect Nodes")
|
||||||
|
|
||||||
|
# Convert str to datetime format
|
||||||
|
dt_format = guess_date_format(self.from_date)
|
||||||
|
from_date = datetime.datetime.strptime(self.from_date, dt_format)
|
||||||
|
to_date = datetime.datetime.strptime(self.to_date, dt_format)
|
||||||
|
|
||||||
|
if self.algorithm == "BFS":
|
||||||
|
self.bfs(from_date, to_date)
|
||||||
|
|
||||||
|
if self.algorithm == "DFS":
|
||||||
|
self.dfs(from_date, to_date)
|
||||||
|
|
||||||
|
# set root as current node
|
||||||
|
root = frappe.db.get_all("Bisect Nodes", filters={"root": ["is", "not set"]})[0]
|
||||||
|
self.get_report_summary()
|
||||||
|
self.current_node = root.name
|
||||||
|
self.current_from_date = self.from_date
|
||||||
|
self.current_to_date = self.to_date
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def get_report_summary(self):
|
||||||
|
filters = {
|
||||||
|
"company": self.company,
|
||||||
|
"filter_based_on": "Date Range",
|
||||||
|
"period_start_date": self.current_from_date,
|
||||||
|
"period_end_date": self.current_to_date,
|
||||||
|
"periodicity": "Yearly",
|
||||||
|
}
|
||||||
|
pl_summary = frappe.get_doc("Report", "Profit and Loss Statement")
|
||||||
|
self.p_l_summary = pl_summary.execute_script_report(filters=filters)[5]
|
||||||
|
bs_summary = frappe.get_doc("Report", "Balance Sheet")
|
||||||
|
self.b_s_summary = bs_summary.execute_script_report(filters=filters)[5]
|
||||||
|
self.difference = abs(self.p_l_summary - self.b_s_summary)
|
||||||
|
|
||||||
|
def update_node(self):
|
||||||
|
current_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||||
|
current_node.balance_sheet_summary = self.b_s_summary
|
||||||
|
current_node.profit_loss_summary = self.p_l_summary
|
||||||
|
current_node.difference = self.difference
|
||||||
|
current_node.generated = True
|
||||||
|
current_node.save()
|
||||||
|
|
||||||
|
def current_node_has_summary_info(self):
|
||||||
|
"Assertion method"
|
||||||
|
return frappe.db.get_value("Bisect Nodes", self.current_node, "generated")
|
||||||
|
|
||||||
|
def fetch_summary_info_from_current_node(self):
|
||||||
|
current_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||||
|
self.p_l_summary = current_node.balance_sheet_summary
|
||||||
|
self.b_s_summary = current_node.profit_loss_summary
|
||||||
|
self.difference = abs(self.p_l_summary - self.b_s_summary)
|
||||||
|
|
||||||
|
def fetch_or_calculate(self):
|
||||||
|
if self.current_node_has_summary_info():
|
||||||
|
self.fetch_summary_info_from_current_node()
|
||||||
|
else:
|
||||||
|
self.get_report_summary()
|
||||||
|
self.update_node()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def bisect_left(self):
|
||||||
|
if self.current_node is not None:
|
||||||
|
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||||
|
if cur_node.left_child is not None:
|
||||||
|
lft_node = frappe.get_doc("Bisect Nodes", cur_node.left_child)
|
||||||
|
self.current_node = cur_node.left_child
|
||||||
|
self.current_from_date = lft_node.period_from_date
|
||||||
|
self.current_to_date = lft_node.period_to_date
|
||||||
|
self.fetch_or_calculate()
|
||||||
|
self.save()
|
||||||
|
else:
|
||||||
|
frappe.msgprint(_("No more children on Left"))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def bisect_right(self):
|
||||||
|
if self.current_node is not None:
|
||||||
|
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||||
|
if cur_node.right_child is not None:
|
||||||
|
rgt_node = frappe.get_doc("Bisect Nodes", cur_node.right_child)
|
||||||
|
self.current_node = cur_node.right_child
|
||||||
|
self.current_from_date = rgt_node.period_from_date
|
||||||
|
self.current_to_date = rgt_node.period_to_date
|
||||||
|
self.fetch_or_calculate()
|
||||||
|
self.save()
|
||||||
|
else:
|
||||||
|
frappe.msgprint(_("No more children on Right"))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def move_up(self):
|
||||||
|
if self.current_node is not None:
|
||||||
|
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||||
|
if cur_node.root is not None:
|
||||||
|
root = frappe.get_doc("Bisect Nodes", cur_node.root)
|
||||||
|
self.current_node = cur_node.root
|
||||||
|
self.current_from_date = root.period_from_date
|
||||||
|
self.current_to_date = root.period_to_date
|
||||||
|
self.fetch_or_calculate()
|
||||||
|
self.save()
|
||||||
|
else:
|
||||||
|
frappe.msgprint(_("Reached Root"))
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestBisectAccountingStatements(FrappeTestCase):
|
||||||
|
pass
|
||||||
0
erpnext/accounts/doctype/bisect_nodes/__init__.py
Normal file
0
erpnext/accounts/doctype/bisect_nodes/__init__.py
Normal file
8
erpnext/accounts/doctype/bisect_nodes/bisect_nodes.js
Normal file
8
erpnext/accounts/doctype/bisect_nodes/bisect_nodes.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
// frappe.ui.form.on("Bisect Nodes", {
|
||||||
|
// refresh(frm) {
|
||||||
|
|
||||||
|
// },
|
||||||
|
// });
|
||||||
97
erpnext/accounts/doctype/bisect_nodes/bisect_nodes.json
Normal file
97
erpnext/accounts/doctype/bisect_nodes/bisect_nodes.json
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "autoincrement",
|
||||||
|
"creation": "2023-09-27 14:56:38.112462",
|
||||||
|
"default_view": "List",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"root",
|
||||||
|
"left_child",
|
||||||
|
"right_child",
|
||||||
|
"period_from_date",
|
||||||
|
"period_to_date",
|
||||||
|
"difference",
|
||||||
|
"balance_sheet_summary",
|
||||||
|
"profit_loss_summary",
|
||||||
|
"generated"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "root",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Root",
|
||||||
|
"options": "Bisect Nodes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "left_child",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Left Child",
|
||||||
|
"options": "Bisect Nodes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "right_child",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Right Child",
|
||||||
|
"options": "Bisect Nodes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "period_from_date",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"label": "Period_from_date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "period_to_date",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"label": "Period To Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "difference",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Difference"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "balance_sheet_summary",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Balance Sheet Summary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "profit_loss_summary",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Profit and Loss Summary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "generated",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Generated"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2023-12-01 17:46:12.437996",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Bisect Nodes",
|
||||||
|
"naming_rule": "Autoincrement",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Administrator",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"read_only": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
|
}
|
||||||
29
erpnext/accounts/doctype/bisect_nodes/bisect_nodes.py
Normal file
29
erpnext/accounts/doctype/bisect_nodes/bisect_nodes.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class BisectNodes(Document):
|
||||||
|
# begin: auto-generated types
|
||||||
|
# This code is auto-generated. Do not modify anything in this block.
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from frappe.types import DF
|
||||||
|
|
||||||
|
balance_sheet_summary: DF.Float
|
||||||
|
difference: DF.Float
|
||||||
|
generated: DF.Check
|
||||||
|
left_child: DF.Link | None
|
||||||
|
name: DF.Int | None
|
||||||
|
period_from_date: DF.Datetime | None
|
||||||
|
period_to_date: DF.Datetime | None
|
||||||
|
profit_loss_summary: DF.Float
|
||||||
|
right_child: DF.Link | None
|
||||||
|
root: DF.Link | None
|
||||||
|
# end: auto-generated types
|
||||||
|
|
||||||
|
pass
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestBisectNodes(FrappeTestCase):
|
||||||
|
pass
|
||||||
@@ -1,457 +1,152 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_events_in_timeline": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"beta": 0,
|
|
||||||
"creation": "2018-06-18 16:51:49.994750",
|
"creation": "2018-06-18 16:51:49.994750",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"naming_series",
|
||||||
|
"user",
|
||||||
|
"date",
|
||||||
|
"from_time",
|
||||||
|
"time",
|
||||||
|
"expense",
|
||||||
|
"custody",
|
||||||
|
"returns",
|
||||||
|
"outstanding_amount",
|
||||||
|
"payments",
|
||||||
|
"net_amount",
|
||||||
|
"amended_from"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "POS-CLO-",
|
"default": "POS-CLO-",
|
||||||
"fieldname": "naming_series",
|
"fieldname": "naming_series",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Series",
|
"label": "Series",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "POS-CLO-",
|
"options": "POS-CLO-",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "user",
|
"fieldname": "user",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "User",
|
"label": "User",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "User",
|
"options": "User",
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"remember_last_selected_value": 0,
|
"reqd": 1
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "Today",
|
"default": "Today",
|
||||||
"fieldname": "date",
|
"fieldname": "date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Date",
|
"label": "Date",
|
||||||
"length": 0,
|
"read_only": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "from_time",
|
"fieldname": "from_time",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Time",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "From Time",
|
"label": "From Time",
|
||||||
"length": 0,
|
"reqd": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "",
|
|
||||||
"fieldname": "time",
|
"fieldname": "time",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Time",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "To Time",
|
"label": "To Time",
|
||||||
"length": 0,
|
"reqd": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "0.00",
|
"default": "0.00",
|
||||||
"fieldname": "expense",
|
"fieldname": "expense",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
"label": "Expense"
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Expense",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "0.00",
|
"default": "0.00",
|
||||||
"fieldname": "custody",
|
"fieldname": "custody",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
"label": "Custody"
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Custody",
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "0.00",
|
"default": "0.00",
|
||||||
"fieldname": "returns",
|
"fieldname": "returns",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Returns",
|
"label": "Returns",
|
||||||
"length": 0,
|
"precision": "2"
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "2",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "0.00",
|
"default": "0.00",
|
||||||
"fieldname": "outstanding_amount",
|
"fieldname": "outstanding_amount",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Outstanding Amount",
|
"label": "Outstanding Amount",
|
||||||
"length": 0,
|
"read_only": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"default": "0.0",
|
|
||||||
"fieldname": "payments",
|
"fieldname": "payments",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Payments",
|
"label": "Payments",
|
||||||
"length": 0,
|
"options": "Cashier Closing Payments"
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Cashier Closing Payments",
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "net_amount",
|
"fieldname": "net_amount",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 1,
|
"in_filter": 1,
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Net Amount",
|
"label": "Net Amount",
|
||||||
"length": 0,
|
"read_only": 1
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "amended_from",
|
"fieldname": "amended_from",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Amended From",
|
"label": "Amended From",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Cashier Closing",
|
"options": "Cashier Closing",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
"read_only": 1
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 0,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"issingle": 0,
|
"links": [],
|
||||||
"istable": 0,
|
"modified": "2023-12-28 13:15:46.858427",
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2019-02-19 08:35:24.157327",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Cashier Closing",
|
"name": "Cashier Closing",
|
||||||
"name_case": "",
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
|
||||||
"cancel": 0,
|
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 1,
|
"export": 1,
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 1,
|
"submit": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1,
|
"states": [],
|
||||||
"track_seen": 0,
|
"track_changes": 1
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
"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",
|
||||||
@@ -158,8 +159,7 @@
|
|||||||
"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",
|
||||||
@@ -178,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",
|
||||||
@@ -296,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-11-08 12:20:23.031733",
|
"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
|
||||||
|
|
||||||
|
|||||||
@@ -1031,6 +1031,10 @@ class JournalEntry(AccountsController):
|
|||||||
|
|
||||||
def build_gl_map(self):
|
def build_gl_map(self):
|
||||||
gl_map = []
|
gl_map = []
|
||||||
|
conversion_rate_map = self.get_conversion_rate_map()
|
||||||
|
transaction_currency_map = self.get_transaction_currency_map()
|
||||||
|
company_currency = erpnext.get_company_currency(self.company)
|
||||||
|
|
||||||
self.get_against_accounts()
|
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"):
|
||||||
@@ -1060,6 +1064,12 @@ class JournalEntry(AccountsController):
|
|||||||
"cost_center": d.cost_center,
|
"cost_center": d.cost_center,
|
||||||
"project": d.project,
|
"project": d.project,
|
||||||
"finance_book": self.finance_book,
|
"finance_book": self.finance_book,
|
||||||
|
"conversion_rate": conversion_rate_map.get(d.against_account_link, 1)
|
||||||
|
if d.account_currency == company_currency
|
||||||
|
else 1,
|
||||||
|
"currency": transaction_currency_map.get(d.against_account_link, d.account_currency)
|
||||||
|
if d.account_currency == company_currency
|
||||||
|
else d.account_currency,
|
||||||
},
|
},
|
||||||
item=d,
|
item=d,
|
||||||
)
|
)
|
||||||
@@ -1109,6 +1119,20 @@ class JournalEntry(AccountsController):
|
|||||||
|
|
||||||
return gl_map
|
return gl_map
|
||||||
|
|
||||||
|
def get_transaction_currency_map(self):
|
||||||
|
transaction_currency_map = {}
|
||||||
|
for account in self.get("accounts"):
|
||||||
|
transaction_currency_map.setdefault(account.party or account.account, account.account_currency)
|
||||||
|
|
||||||
|
return transaction_currency_map
|
||||||
|
|
||||||
|
def get_conversion_rate_map(self):
|
||||||
|
conversion_rate_map = {}
|
||||||
|
for account in self.get("accounts"):
|
||||||
|
conversion_rate_map.setdefault(account.party or account.account, account.exchange_rate)
|
||||||
|
|
||||||
|
return conversion_rate_map
|
||||||
|
|
||||||
def make_gl_entries(self, cancel=0, adv_adj=0):
|
def make_gl_entries(self, cancel=0, adv_adj=0):
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -765,7 +765,7 @@ def get_pos_reserved_qty(item_code, warehouse):
|
|||||||
reserved_qty = (
|
reserved_qty = (
|
||||||
frappe.qb.from_(p_inv)
|
frappe.qb.from_(p_inv)
|
||||||
.from_(p_item)
|
.from_(p_item)
|
||||||
.select(Sum(p_item.qty).as_("qty"))
|
.select(Sum(p_item.stock_qty).as_("stock_qty"))
|
||||||
.where(
|
.where(
|
||||||
(p_inv.name == p_item.parent)
|
(p_inv.name == p_item.parent)
|
||||||
& (IfNull(p_inv.consolidated_invoice, "") == "")
|
& (IfNull(p_inv.consolidated_invoice, "") == "")
|
||||||
@@ -775,7 +775,7 @@ def get_pos_reserved_qty(item_code, warehouse):
|
|||||||
)
|
)
|
||||||
).run(as_dict=True)
|
).run(as_dict=True)
|
||||||
|
|
||||||
return reserved_qty[0].qty or 0 if reserved_qty else 0
|
return flt(reserved_qty[0].stock_qty) if reserved_qty else 0
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -440,7 +440,7 @@ def reconcile(doc: None | str = None) -> None:
|
|||||||
# Update the parent doc about the exception
|
# Update the parent doc about the exception
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
|
|
||||||
traceback = frappe.get_traceback()
|
traceback = frappe.get_traceback(with_context=True)
|
||||||
if traceback:
|
if traceback:
|
||||||
message = "Traceback: <br>" + traceback
|
message = "Traceback: <br>" + traceback
|
||||||
frappe.db.set_value("Process Payment Reconciliation Log", log, "error_log", message)
|
frappe.db.set_value("Process Payment Reconciliation Log", log, "error_log", message)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
|||||||
super.onload();
|
super.onload();
|
||||||
|
|
||||||
// Ignore linked advances
|
// Ignore linked advances
|
||||||
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
|
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries", "Serial and Batch Bundle"];
|
||||||
|
|
||||||
if(!this.frm.doc.__islocal) {
|
if(!this.frm.doc.__islocal) {
|
||||||
// show credit_to in print format
|
// show credit_to in print format
|
||||||
@@ -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);
|
||||||
|
|
||||||
@@ -396,6 +408,8 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
|||||||
}
|
}
|
||||||
|
|
||||||
on_submit() {
|
on_submit() {
|
||||||
|
super.on_submit();
|
||||||
|
|
||||||
$.each(this.frm.doc["items"] || [], function(i, row) {
|
$.each(this.frm.doc["items"] || [], function(i, row) {
|
||||||
if(row.purchase_receipt) frappe.model.clear_doc("Purchase Receipt", row.purchase_receipt)
|
if(row.purchase_receipt) frappe.model.clear_doc("Purchase Receipt", row.purchase_receipt)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1157,11 +1157,17 @@ class PurchaseInvoice(BuyingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
assets = frappe.db.get_all(
|
assets = frappe.db.get_all(
|
||||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
"Asset",
|
||||||
|
filters={"purchase_invoice": self.name, "item_code": item.item_code},
|
||||||
|
fields=["name", "asset_quantity"],
|
||||||
)
|
)
|
||||||
for asset in assets:
|
for asset in assets:
|
||||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
frappe.db.set_value(
|
||||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
|
"Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate) * asset.asset_quantity
|
||||||
|
)
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate) * asset.asset_quantity
|
||||||
|
)
|
||||||
|
|
||||||
def make_stock_adjustment_entry(
|
def make_stock_adjustment_entry(
|
||||||
self, gl_entries, item, voucher_wise_stock_value, account_currency
|
self, gl_entries, item, voucher_wise_stock_value, account_currency
|
||||||
@@ -1858,10 +1864,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):
|
||||||
|
|||||||
@@ -1985,6 +1985,26 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
|||||||
|
|
||||||
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
|
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
|
||||||
|
|
||||||
|
def test_debit_note_without_item(self):
|
||||||
|
pi = make_purchase_invoice(item_name="_Test Item", qty=10, do_not_submit=True)
|
||||||
|
pi.items[0].item_code = ""
|
||||||
|
pi.save()
|
||||||
|
|
||||||
|
self.assertFalse(pi.items[0].item_code)
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
return_pi = make_purchase_invoice(
|
||||||
|
item_name="_Test Item",
|
||||||
|
is_return=1,
|
||||||
|
return_against=pi.name,
|
||||||
|
qty=-10,
|
||||||
|
do_not_save=True,
|
||||||
|
)
|
||||||
|
return_pi.items[0].item_code = ""
|
||||||
|
return_pi.save()
|
||||||
|
return_pi.submit()
|
||||||
|
self.assertEqual(return_pi.docstatus, 1)
|
||||||
|
|
||||||
|
|
||||||
def set_advance_flag(company, flag, default_account):
|
def set_advance_flag(company, flag, default_account):
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
@@ -2121,6 +2141,7 @@ def make_purchase_invoice(**args):
|
|||||||
"items",
|
"items",
|
||||||
{
|
{
|
||||||
"item_code": args.item or args.item_code or "_Test Item",
|
"item_code": args.item or args.item_code or "_Test Item",
|
||||||
|
"item_name": args.item_name,
|
||||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||||
"qty": args.qty or 5,
|
"qty": args.qty or 5,
|
||||||
"received_qty": args.received_qty or 0,
|
"received_qty": args.received_qty or 0,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ def start_payment_ledger_repost(docname=None):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
|
|
||||||
traceback = frappe.get_traceback()
|
traceback = frappe.get_traceback(with_context=True)
|
||||||
if traceback:
|
if traceback:
|
||||||
message = "Traceback: <br>" + traceback
|
message = "Traceback: <br>" + traceback
|
||||||
frappe.db.set_value(repost_doc.doctype, repost_doc.name, "repost_error_log", message)
|
frappe.db.set_value(repost_doc.doctype, repost_doc.name, "repost_error_log", message)
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
|||||||
super.onload();
|
super.onload();
|
||||||
|
|
||||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
|
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
|
||||||
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
|
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries",
|
||||||
|
'Serial and Batch Bundle'
|
||||||
|
];
|
||||||
|
|
||||||
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
||||||
// show debit_to in print format
|
// show debit_to in print format
|
||||||
@@ -197,6 +199,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
|||||||
on_submit(doc, dt, dn) {
|
on_submit(doc, dt, dn) {
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|
||||||
|
super.on_submit();
|
||||||
if (frappe.get_route()[0] != 'Form') {
|
if (frappe.get_route()[0] != 'Form') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,6 +138,7 @@
|
|||||||
"loyalty_amount",
|
"loyalty_amount",
|
||||||
"column_break_77",
|
"column_break_77",
|
||||||
"loyalty_program",
|
"loyalty_program",
|
||||||
|
"dont_create_loyalty_points",
|
||||||
"loyalty_redemption_account",
|
"loyalty_redemption_account",
|
||||||
"loyalty_redemption_cost_center",
|
"loyalty_redemption_cost_center",
|
||||||
"contact_and_address_tab",
|
"contact_and_address_tab",
|
||||||
@@ -1041,8 +1042,7 @@
|
|||||||
"label": "Loyalty Program",
|
"label": "Loyalty Program",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Loyalty Program",
|
"options": "Loyalty Program",
|
||||||
"print_hide": 1,
|
"print_hide": 1
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
@@ -2162,6 +2162,14 @@
|
|||||||
"fieldname": "update_billed_amount_in_delivery_note",
|
"fieldname": "update_billed_amount_in_delivery_note",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Update Billed Amount in Delivery Note"
|
"label": "Update Billed Amount in Delivery Note"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "loyalty_program",
|
||||||
|
"fieldname": "dont_create_loyalty_points",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Don't Create Loyalty Points",
|
||||||
|
"no_copy": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
@@ -2174,7 +2182,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-11-23 16:56:29.679499",
|
"modified": "2024-01-02 17:25:46.027523",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ class SalesInvoice(SellingController):
|
|||||||
discount_amount: DF.Currency
|
discount_amount: DF.Currency
|
||||||
dispatch_address: DF.SmallText | None
|
dispatch_address: DF.SmallText | None
|
||||||
dispatch_address_name: DF.Link | None
|
dispatch_address_name: DF.Link | None
|
||||||
|
dont_create_loyalty_points: DF.Check
|
||||||
due_date: DF.Date | None
|
due_date: DF.Date | None
|
||||||
from_date: DF.Date | None
|
from_date: DF.Date | None
|
||||||
grand_total: DF.Currency
|
grand_total: DF.Currency
|
||||||
@@ -471,7 +472,12 @@ class SalesInvoice(SellingController):
|
|||||||
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||||
|
|
||||||
# create the loyalty point ledger entry if the customer is enrolled in any loyalty program
|
# create the loyalty point ledger entry if the customer is enrolled in any loyalty program
|
||||||
if not self.is_return and not self.is_consolidated and self.loyalty_program:
|
if (
|
||||||
|
not self.is_return
|
||||||
|
and not self.is_consolidated
|
||||||
|
and self.loyalty_program
|
||||||
|
and not self.dont_create_loyalty_points
|
||||||
|
):
|
||||||
self.make_loyalty_point_entry()
|
self.make_loyalty_point_entry()
|
||||||
elif (
|
elif (
|
||||||
self.is_return and self.return_against and not self.is_consolidated and self.loyalty_program
|
self.is_return and self.return_against and not self.is_consolidated and self.loyalty_program
|
||||||
@@ -586,6 +592,8 @@ class SalesInvoice(SellingController):
|
|||||||
"Serial and Batch Bundle",
|
"Serial and Batch Bundle",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.delete_auto_created_batches()
|
||||||
|
|
||||||
def update_status_updater_args(self):
|
def update_status_updater_args(self):
|
||||||
if cint(self.update_stock):
|
if cint(self.update_stock):
|
||||||
self.status_updater.append(
|
self.status_updater.append(
|
||||||
@@ -2575,10 +2583,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)
|
||||||
|
|||||||
@@ -1414,10 +1414,11 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
|
|
||||||
def test_serialized_cancel(self):
|
def test_serialized_cancel(self):
|
||||||
si = self.test_serialized()
|
si = self.test_serialized()
|
||||||
si.cancel()
|
si.reload()
|
||||||
|
|
||||||
serial_nos = get_serial_nos_from_bundle(si.get("items")[0].serial_and_batch_bundle)
|
serial_nos = get_serial_nos_from_bundle(si.get("items")[0].serial_and_batch_bundle)
|
||||||
|
|
||||||
|
si.cancel()
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
frappe.db.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC"
|
frappe.db.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
"warehouse",
|
"warehouse",
|
||||||
"target_warehouse",
|
"target_warehouse",
|
||||||
"quality_inspection",
|
"quality_inspection",
|
||||||
|
"pick_serial_and_batch",
|
||||||
"serial_and_batch_bundle",
|
"serial_and_batch_bundle",
|
||||||
"batch_no",
|
"batch_no",
|
||||||
"incoming_rate",
|
"incoming_rate",
|
||||||
@@ -897,12 +898,18 @@
|
|||||||
"options": "Serial and Batch Bundle",
|
"options": "Serial and Batch Bundle",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:parent.update_stock === 1",
|
||||||
|
"fieldname": "pick_serial_and_batch",
|
||||||
|
"fieldtype": "Button",
|
||||||
|
"label": "Pick Serial / Batch No"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-14 18:34:10.479329",
|
"modified": "2023-12-29 13:03:14.121298",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Item",
|
"name": "Sales Invoice Item",
|
||||||
|
|||||||
@@ -148,13 +148,13 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "additional_discount_percentage",
|
"fieldname": "additional_discount_percentage",
|
||||||
"fieldtype": "Percent",
|
"fieldtype": "Percent",
|
||||||
"label": "Additional DIscount Percentage"
|
"label": "Additional Discount Percentage"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "additional_discount_amount",
|
"fieldname": "additional_discount_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Additional DIscount Amount"
|
"label": "Additional Discount Amount"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
@@ -267,7 +267,7 @@
|
|||||||
"link_fieldname": "subscription"
|
"link_fieldname": "subscription"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-09-18 17:48:21.900252",
|
"modified": "2023-12-28 17:20:42.687789",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Subscription",
|
"name": "Subscription",
|
||||||
|
|||||||
@@ -356,18 +356,20 @@ class Subscription(Document):
|
|||||||
self,
|
self,
|
||||||
from_date: Optional[Union[str, datetime.date]] = None,
|
from_date: Optional[Union[str, datetime.date]] = None,
|
||||||
to_date: Optional[Union[str, datetime.date]] = None,
|
to_date: Optional[Union[str, datetime.date]] = None,
|
||||||
|
posting_date: Optional[Union[str, datetime.date]] = None,
|
||||||
) -> Document:
|
) -> Document:
|
||||||
"""
|
"""
|
||||||
Creates a `Invoice` for the `Subscription`, updates `self.invoices` and
|
Creates a `Invoice` for the `Subscription`, updates `self.invoices` and
|
||||||
saves the `Subscription`.
|
saves the `Subscription`.
|
||||||
Backwards compatibility
|
Backwards compatibility
|
||||||
"""
|
"""
|
||||||
return self.create_invoice(from_date=from_date, to_date=to_date)
|
return self.create_invoice(from_date=from_date, to_date=to_date, posting_date=posting_date)
|
||||||
|
|
||||||
def create_invoice(
|
def create_invoice(
|
||||||
self,
|
self,
|
||||||
from_date: Optional[Union[str, datetime.date]] = None,
|
from_date: Optional[Union[str, datetime.date]] = None,
|
||||||
to_date: Optional[Union[str, datetime.date]] = None,
|
to_date: Optional[Union[str, datetime.date]] = None,
|
||||||
|
posting_date: Optional[Union[str, datetime.date]] = None,
|
||||||
) -> Document:
|
) -> Document:
|
||||||
"""
|
"""
|
||||||
Creates a `Invoice`, submits it and returns it
|
Creates a `Invoice`, submits it and returns it
|
||||||
@@ -385,11 +387,13 @@ class Subscription(Document):
|
|||||||
invoice = frappe.new_doc(self.invoice_document_type)
|
invoice = frappe.new_doc(self.invoice_document_type)
|
||||||
invoice.company = company
|
invoice.company = company
|
||||||
invoice.set_posting_time = 1
|
invoice.set_posting_time = 1
|
||||||
invoice.posting_date = (
|
|
||||||
self.current_invoice_start
|
if self.generate_invoice_at == "Beginning of the current subscription period":
|
||||||
if self.generate_invoice_at == "Beginning of the current subscription period"
|
invoice.posting_date = self.current_invoice_start
|
||||||
else self.current_invoice_end
|
elif self.generate_invoice_at == "Days before the current subscription period":
|
||||||
)
|
invoice.posting_date = posting_date or self.current_invoice_start
|
||||||
|
else:
|
||||||
|
invoice.posting_date = self.current_invoice_end
|
||||||
|
|
||||||
invoice.cost_center = self.cost_center
|
invoice.cost_center = self.cost_center
|
||||||
|
|
||||||
@@ -413,6 +417,7 @@ class Subscription(Document):
|
|||||||
# Subscription is better suited for service items. I won't update `update_stock`
|
# Subscription is better suited for service items. I won't update `update_stock`
|
||||||
# for that reason
|
# for that reason
|
||||||
items_list = self.get_items_from_plans(self.plans, is_prorate())
|
items_list = self.get_items_from_plans(self.plans, is_prorate())
|
||||||
|
|
||||||
for item in items_list:
|
for item in items_list:
|
||||||
item["cost_center"] = self.cost_center
|
item["cost_center"] = self.cost_center
|
||||||
invoice.append("items", item)
|
invoice.append("items", item)
|
||||||
@@ -556,7 +561,7 @@ class Subscription(Document):
|
|||||||
if not self.is_current_invoice_generated(
|
if not self.is_current_invoice_generated(
|
||||||
self.current_invoice_start, self.current_invoice_end
|
self.current_invoice_start, self.current_invoice_end
|
||||||
) and self.can_generate_new_invoice(posting_date):
|
) and self.can_generate_new_invoice(posting_date):
|
||||||
self.generate_invoice()
|
self.generate_invoice(posting_date=posting_date)
|
||||||
self.update_subscription_period(add_days(self.current_invoice_end, 1))
|
self.update_subscription_period(add_days(self.current_invoice_end, 1))
|
||||||
|
|
||||||
if self.cancel_at_period_end and (
|
if self.cancel_at_period_end and (
|
||||||
|
|||||||
@@ -114,14 +114,12 @@ def _get_party_details(
|
|||||||
set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype)
|
set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype)
|
||||||
)
|
)
|
||||||
party = party_details[party_type.lower()]
|
party = party_details[party_type.lower()]
|
||||||
|
|
||||||
if not ignore_permissions and not (
|
|
||||||
frappe.has_permission(party_type, "read", party)
|
|
||||||
or frappe.has_permission(party_type, "select", party)
|
|
||||||
):
|
|
||||||
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
|
|
||||||
|
|
||||||
party = frappe.get_doc(party_type, party)
|
party = frappe.get_doc(party_type, party)
|
||||||
|
|
||||||
|
if not ignore_permissions:
|
||||||
|
ptype = "select" if frappe.only_has_select_perm(party_type) else "read"
|
||||||
|
frappe.has_permission(party_type, ptype, party, throw=True)
|
||||||
|
|
||||||
currency = party.get("default_currency") or currency or get_company_currency(company)
|
currency = party.get("default_currency") or currency or get_company_currency(company)
|
||||||
|
|
||||||
party_address, shipping_address = set_address_details(
|
party_address, shipping_address = set_address_details(
|
||||||
@@ -637,9 +635,7 @@ def get_due_date_from_template(template_name, posting_date, bill_date):
|
|||||||
return due_date
|
return due_date
|
||||||
|
|
||||||
|
|
||||||
def validate_due_date(
|
def validate_due_date(posting_date, due_date, bill_date=None, template_name=None):
|
||||||
posting_date, due_date, party_type, party, company=None, bill_date=None, template_name=None
|
|
||||||
):
|
|
||||||
if getdate(due_date) < getdate(posting_date):
|
if getdate(due_date) < getdate(posting_date):
|
||||||
frappe.throw(_("Due Date cannot be before Posting / Supplier Invoice Date"))
|
frappe.throw(_("Due Date cannot be before Posting / Supplier Invoice Date"))
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -25,11 +25,26 @@ frappe.query_reports["Asset Depreciations and Balances"] = {
|
|||||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"group_by",
|
||||||
|
"label": __("Group By"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": ["Asset Category", "Asset"],
|
||||||
|
"default": "Asset Category",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"asset_category",
|
"fieldname":"asset_category",
|
||||||
"label": __("Asset Category"),
|
"label": __("Asset Category"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Asset Category"
|
"options": "Asset Category",
|
||||||
}
|
"depends_on": "eval: doc.group_by == 'Asset Category'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"asset",
|
||||||
|
"label": __("Asset"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Asset",
|
||||||
|
"depends_on": "eval: doc.group_by == 'Asset'",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,17 @@ def execute(filters=None):
|
|||||||
|
|
||||||
|
|
||||||
def get_data(filters):
|
def get_data(filters):
|
||||||
|
if filters.get("group_by") == "Asset Category":
|
||||||
|
return get_group_by_asset_category_data(filters)
|
||||||
|
elif filters.get("group_by") == "Asset":
|
||||||
|
return get_group_by_asset_data(filters)
|
||||||
|
|
||||||
|
|
||||||
|
def get_group_by_asset_category_data(filters):
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
asset_categories = get_asset_categories(filters)
|
asset_categories = get_asset_categories_for_grouped_by_category(filters)
|
||||||
assets = get_assets(filters)
|
assets = get_assets_for_grouped_by_category(filters)
|
||||||
|
|
||||||
for asset_category in asset_categories:
|
for asset_category in asset_categories:
|
||||||
row = frappe._dict()
|
row = frappe._dict()
|
||||||
@@ -38,6 +45,7 @@ def get_data(filters):
|
|||||||
if asset["asset_category"] == asset_category.get("asset_category", "")
|
if asset["asset_category"] == asset_category.get("asset_category", "")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
row.accumulated_depreciation_as_on_to_date = (
|
row.accumulated_depreciation_as_on_to_date = (
|
||||||
flt(row.accumulated_depreciation_as_on_from_date)
|
flt(row.accumulated_depreciation_as_on_from_date)
|
||||||
+ flt(row.depreciation_amount_during_the_period)
|
+ flt(row.depreciation_amount_during_the_period)
|
||||||
@@ -57,7 +65,7 @@ def get_data(filters):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def get_asset_categories(filters):
|
def get_asset_categories_for_grouped_by_category(filters):
|
||||||
condition = ""
|
condition = ""
|
||||||
if filters.get("asset_category"):
|
if filters.get("asset_category"):
|
||||||
condition += " and asset_category = %(asset_category)s"
|
condition += " and asset_category = %(asset_category)s"
|
||||||
@@ -116,7 +124,105 @@ def get_asset_categories(filters):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_assets(filters):
|
def get_asset_details_for_grouped_by_category(filters):
|
||||||
|
condition = ""
|
||||||
|
if filters.get("asset"):
|
||||||
|
condition += " and name = %(asset)s"
|
||||||
|
return frappe.db.sql(
|
||||||
|
"""
|
||||||
|
SELECT name,
|
||||||
|
ifnull(sum(case when purchase_date < %(from_date)s then
|
||||||
|
case when ifnull(disposal_date, 0) = 0 or disposal_date >= %(from_date)s then
|
||||||
|
gross_purchase_amount
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as cost_as_on_from_date,
|
||||||
|
ifnull(sum(case when purchase_date >= %(from_date)s then
|
||||||
|
gross_purchase_amount
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as cost_of_new_purchase,
|
||||||
|
ifnull(sum(case when ifnull(disposal_date, 0) != 0
|
||||||
|
and disposal_date >= %(from_date)s
|
||||||
|
and disposal_date <= %(to_date)s then
|
||||||
|
case when status = "Sold" then
|
||||||
|
gross_purchase_amount
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as cost_of_sold_asset,
|
||||||
|
ifnull(sum(case when ifnull(disposal_date, 0) != 0
|
||||||
|
and disposal_date >= %(from_date)s
|
||||||
|
and disposal_date <= %(to_date)s then
|
||||||
|
case when status = "Scrapped" then
|
||||||
|
gross_purchase_amount
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as cost_of_scrapped_asset
|
||||||
|
from `tabAsset`
|
||||||
|
where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s {}
|
||||||
|
group by name
|
||||||
|
""".format(
|
||||||
|
condition
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"to_date": filters.to_date,
|
||||||
|
"from_date": filters.from_date,
|
||||||
|
"company": filters.company,
|
||||||
|
"asset": filters.get("asset"),
|
||||||
|
},
|
||||||
|
as_dict=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_group_by_asset_data(filters):
|
||||||
|
data = []
|
||||||
|
|
||||||
|
asset_details = get_asset_details_for_grouped_by_category(filters)
|
||||||
|
assets = get_assets_for_grouped_by_asset(filters)
|
||||||
|
|
||||||
|
for asset_detail in asset_details:
|
||||||
|
row = frappe._dict()
|
||||||
|
# row.asset_category = asset_category
|
||||||
|
row.update(asset_detail)
|
||||||
|
|
||||||
|
row.cost_as_on_to_date = (
|
||||||
|
flt(row.cost_as_on_from_date)
|
||||||
|
+ flt(row.cost_of_new_purchase)
|
||||||
|
- flt(row.cost_of_sold_asset)
|
||||||
|
- flt(row.cost_of_scrapped_asset)
|
||||||
|
)
|
||||||
|
|
||||||
|
row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", "")))
|
||||||
|
|
||||||
|
row.accumulated_depreciation_as_on_to_date = (
|
||||||
|
flt(row.accumulated_depreciation_as_on_from_date)
|
||||||
|
+ flt(row.depreciation_amount_during_the_period)
|
||||||
|
- flt(row.depreciation_eliminated_during_the_period)
|
||||||
|
)
|
||||||
|
|
||||||
|
row.net_asset_value_as_on_from_date = flt(row.cost_as_on_from_date) - flt(
|
||||||
|
row.accumulated_depreciation_as_on_from_date
|
||||||
|
)
|
||||||
|
|
||||||
|
row.net_asset_value_as_on_to_date = flt(row.cost_as_on_to_date) - flt(
|
||||||
|
row.accumulated_depreciation_as_on_to_date
|
||||||
|
)
|
||||||
|
|
||||||
|
data.append(row)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def get_assets_for_grouped_by_category(filters):
|
||||||
condition = ""
|
condition = ""
|
||||||
if filters.get("asset_category"):
|
if filters.get("asset_category"):
|
||||||
condition = " and a.asset_category = '{}'".format(filters.get("asset_category"))
|
condition = " and a.asset_category = '{}'".format(filters.get("asset_category"))
|
||||||
@@ -178,15 +284,93 @@ def get_assets(filters):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_assets_for_grouped_by_asset(filters):
|
||||||
|
condition = ""
|
||||||
|
if filters.get("asset"):
|
||||||
|
condition = " and a.name = '{}'".format(filters.get("asset"))
|
||||||
|
return frappe.db.sql(
|
||||||
|
"""
|
||||||
|
SELECT results.name as asset,
|
||||||
|
sum(results.accumulated_depreciation_as_on_from_date) as accumulated_depreciation_as_on_from_date,
|
||||||
|
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
|
||||||
|
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
|
||||||
|
from (SELECT a.name as name,
|
||||||
|
ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
|
||||||
|
gle.debit
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as accumulated_depreciation_as_on_from_date,
|
||||||
|
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
|
||||||
|
and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then
|
||||||
|
gle.debit
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as depreciation_eliminated_during_the_period,
|
||||||
|
ifnull(sum(case when gle.posting_date >= %(from_date)s and gle.posting_date <= %(to_date)s
|
||||||
|
and (ifnull(a.disposal_date, 0) = 0 or gle.posting_date <= a.disposal_date) then
|
||||||
|
gle.debit
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as depreciation_amount_during_the_period
|
||||||
|
from `tabGL Entry` gle
|
||||||
|
join `tabAsset` a on
|
||||||
|
gle.against_voucher = a.name
|
||||||
|
join `tabAsset Category Account` aca on
|
||||||
|
aca.parent = a.asset_category and aca.company_name = %(company)s
|
||||||
|
join `tabCompany` company on
|
||||||
|
company.name = %(company)s
|
||||||
|
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account) {0}
|
||||||
|
group by a.name
|
||||||
|
union
|
||||||
|
SELECT a.name as name,
|
||||||
|
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then
|
||||||
|
0
|
||||||
|
else
|
||||||
|
a.opening_accumulated_depreciation
|
||||||
|
end), 0) as accumulated_depreciation_as_on_from_date,
|
||||||
|
ifnull(sum(case when a.disposal_date >= %(from_date)s and a.disposal_date <= %(to_date)s then
|
||||||
|
a.opening_accumulated_depreciation
|
||||||
|
else
|
||||||
|
0
|
||||||
|
end), 0) as depreciation_eliminated_during_the_period,
|
||||||
|
0 as depreciation_amount_during_the_period
|
||||||
|
from `tabAsset` a
|
||||||
|
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {0}
|
||||||
|
group by a.name) as results
|
||||||
|
group by results.name
|
||||||
|
""".format(
|
||||||
|
condition
|
||||||
|
),
|
||||||
|
{"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company},
|
||||||
|
as_dict=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_columns(filters):
|
def get_columns(filters):
|
||||||
return [
|
columns = []
|
||||||
|
|
||||||
|
if filters.get("group_by") == "Asset Category":
|
||||||
|
columns.append(
|
||||||
{
|
{
|
||||||
"label": _("Asset Category"),
|
"label": _("Asset Category"),
|
||||||
"fieldname": "asset_category",
|
"fieldname": "asset_category",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Asset Category",
|
"options": "Asset Category",
|
||||||
"width": 120,
|
"width": 120,
|
||||||
},
|
}
|
||||||
|
)
|
||||||
|
elif filters.get("group_by") == "Asset":
|
||||||
|
columns.append(
|
||||||
|
{
|
||||||
|
"label": _("Asset"),
|
||||||
|
"fieldname": "asset",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Asset",
|
||||||
|
"width": 120,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
columns += [
|
||||||
{
|
{
|
||||||
"label": _("Cost as on") + " " + formatdate(filters.day_before_from_date),
|
"label": _("Cost as on") + " " + formatdate(filters.day_before_from_date),
|
||||||
"fieldname": "cost_as_on_from_date",
|
"fieldname": "cost_as_on_from_date",
|
||||||
@@ -254,3 +438,5 @@ def get_columns(filters):
|
|||||||
"width": 200,
|
"width": 200,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
return columns
|
||||||
|
|||||||
@@ -97,11 +97,11 @@ def execute(filters=None):
|
|||||||
|
|
||||||
chart = get_chart_data(filters, columns, asset, liability, equity)
|
chart = get_chart_data(filters, columns, asset, liability, equity)
|
||||||
|
|
||||||
report_summary = get_report_summary(
|
report_summary, primitive_summary = get_report_summary(
|
||||||
period_list, asset, liability, equity, provisional_profit_loss, currency, filters
|
period_list, asset, liability, equity, provisional_profit_loss, currency, filters
|
||||||
)
|
)
|
||||||
|
|
||||||
return columns, data, message, chart, report_summary
|
return columns, data, message, chart, report_summary, primitive_summary
|
||||||
|
|
||||||
|
|
||||||
def get_provisional_profit_loss(
|
def get_provisional_profit_loss(
|
||||||
@@ -217,7 +217,7 @@ def get_report_summary(
|
|||||||
"datatype": "Currency",
|
"datatype": "Currency",
|
||||||
"currency": currency,
|
"currency": currency,
|
||||||
},
|
},
|
||||||
]
|
], (net_asset - net_liability + net_equity)
|
||||||
|
|
||||||
|
|
||||||
def get_chart_data(filters, columns, asset, liability, equity):
|
def get_chart_data(filters, columns, asset, liability, equity):
|
||||||
|
|||||||
@@ -2,7 +2,44 @@
|
|||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
frappe.query_reports["Budget Variance Report"] = {
|
frappe.query_reports["Budget Variance Report"] = {
|
||||||
"filters": [
|
"filters": get_filters(),
|
||||||
|
"formatter": function (value, row, column, data, default_formatter) {
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
|
||||||
|
if (column.fieldname.includes(__("variance"))) {
|
||||||
|
|
||||||
|
if (data[column.fieldname] < 0) {
|
||||||
|
value = "<span style='color:red'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
else if (data[column.fieldname] > 0) {
|
||||||
|
value = "<span style='color:green'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function get_filters() {
|
||||||
|
function get_dimensions() {
|
||||||
|
let result = [];
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.get_dimensions",
|
||||||
|
args: {
|
||||||
|
'with_cost_center_and_project': true
|
||||||
|
},
|
||||||
|
async: false,
|
||||||
|
callback: function(r) {
|
||||||
|
if(!r.exc) {
|
||||||
|
result = r.message[0].map(elem => elem.document_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
let budget_against_options = get_dimensions();
|
||||||
|
|
||||||
|
let filters = [
|
||||||
{
|
{
|
||||||
fieldname: "from_fiscal_year",
|
fieldname: "from_fiscal_year",
|
||||||
label: __("From Fiscal Year"),
|
label: __("From Fiscal Year"),
|
||||||
@@ -44,9 +81,13 @@ frappe.query_reports["Budget Variance Report"] = {
|
|||||||
fieldname: "budget_against",
|
fieldname: "budget_against",
|
||||||
label: __("Budget Against"),
|
label: __("Budget Against"),
|
||||||
fieldtype: "Select",
|
fieldtype: "Select",
|
||||||
options: ["Cost Center", "Project"],
|
options: budget_against_options,
|
||||||
default: "Cost Center",
|
default: "Cost Center",
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
|
get_data: function() {
|
||||||
|
console.log(this.options);
|
||||||
|
return ["Emacs", "Rocks"];
|
||||||
|
},
|
||||||
on_change: function() {
|
on_change: function() {
|
||||||
frappe.query_report.set_filter_value("budget_against_filter", []);
|
frappe.query_report.set_filter_value("budget_against_filter", []);
|
||||||
frappe.query_report.refresh();
|
frappe.query_report.refresh();
|
||||||
@@ -71,24 +112,8 @@ frappe.query_reports["Budget Variance Report"] = {
|
|||||||
fieldtype: "Check",
|
fieldtype: "Check",
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
],
|
]
|
||||||
"formatter": function (value, row, column, data, default_formatter) {
|
|
||||||
value = default_formatter(value, row, column, data);
|
|
||||||
|
|
||||||
if (column.fieldname.includes(__("variance"))) {
|
return filters;
|
||||||
|
|
||||||
if (data[column.fieldname] < 0) {
|
|
||||||
value = "<span style='color:red'>" + value + "</span>";
|
|
||||||
}
|
|
||||||
else if (data[column.fieldname] > 0) {
|
|
||||||
value = "<span style='color:green'>" + value + "</span>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
erpnext.dimension_filters.forEach((dimension) => {
|
|
||||||
frappe.query_reports["Budget Variance Report"].filters[4].options.push(dimension["document_type"]);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, scrub
|
from frappe import _, qb, scrub
|
||||||
from frappe.utils import getdate, nowdate
|
from frappe.utils import getdate, nowdate
|
||||||
|
|
||||||
|
|
||||||
@@ -38,7 +38,6 @@ class PartyLedgerSummaryReport(object):
|
|||||||
"""
|
"""
|
||||||
Additional Columns for 'User Permission' based access control
|
Additional Columns for 'User Permission' based access control
|
||||||
"""
|
"""
|
||||||
from frappe import qb
|
|
||||||
|
|
||||||
if self.filters.party_type == "Customer":
|
if self.filters.party_type == "Customer":
|
||||||
self.territories = frappe._dict({})
|
self.territories = frappe._dict({})
|
||||||
@@ -365,13 +364,29 @@ class PartyLedgerSummaryReport(object):
|
|||||||
|
|
||||||
def get_party_adjustment_amounts(self):
|
def get_party_adjustment_amounts(self):
|
||||||
conditions = self.prepare_conditions()
|
conditions = self.prepare_conditions()
|
||||||
income_or_expense = (
|
account_type = "Expense Account" if self.filters.party_type == "Customer" else "Income Account"
|
||||||
"Expense Account" if self.filters.party_type == "Customer" else "Income Account"
|
income_or_expense_accounts = frappe.db.get_all(
|
||||||
|
"Account", filters={"account_type": account_type, "company": self.filters.company}, pluck="name"
|
||||||
)
|
)
|
||||||
invoice_dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit"
|
invoice_dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit"
|
||||||
reverse_dr_or_cr = "credit" if self.filters.party_type == "Customer" else "debit"
|
reverse_dr_or_cr = "credit" if self.filters.party_type == "Customer" else "debit"
|
||||||
round_off_account = frappe.get_cached_value("Company", self.filters.company, "round_off_account")
|
round_off_account = frappe.get_cached_value("Company", self.filters.company, "round_off_account")
|
||||||
|
|
||||||
|
gl = qb.DocType("GL Entry")
|
||||||
|
if not income_or_expense_accounts:
|
||||||
|
# prevent empty 'in' condition
|
||||||
|
income_or_expense_accounts.append("")
|
||||||
|
|
||||||
|
accounts_query = (
|
||||||
|
qb.from_(gl)
|
||||||
|
.select(gl.voucher_type, gl.voucher_no)
|
||||||
|
.where(
|
||||||
|
(gl.account.isin(income_or_expense_accounts))
|
||||||
|
& (gl.posting_date.gte(self.filters.from_date))
|
||||||
|
& (gl.posting_date.lte(self.filters.to_date))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
gl_entries = frappe.db.sql(
|
gl_entries = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select
|
select
|
||||||
@@ -381,16 +396,15 @@ class PartyLedgerSummaryReport(object):
|
|||||||
where
|
where
|
||||||
docstatus < 2 and is_cancelled = 0
|
docstatus < 2 and is_cancelled = 0
|
||||||
and (voucher_type, voucher_no) in (
|
and (voucher_type, voucher_no) in (
|
||||||
select voucher_type, voucher_no from `tabGL Entry` gle, `tabAccount` acc
|
{accounts_query}
|
||||||
where acc.name = gle.account and acc.account_type = '{income_or_expense}'
|
|
||||||
and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2
|
|
||||||
) and (voucher_type, voucher_no) in (
|
) and (voucher_type, voucher_no) in (
|
||||||
select voucher_type, voucher_no from `tabGL Entry` gle
|
select voucher_type, voucher_no from `tabGL Entry` gle
|
||||||
where gle.party_type=%(party_type)s and ifnull(party, '') != ''
|
where gle.party_type=%(party_type)s and ifnull(party, '') != ''
|
||||||
and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2 {conditions}
|
and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2 {conditions}
|
||||||
)
|
)
|
||||||
""".format(
|
""".format(
|
||||||
conditions=conditions, income_or_expense=income_or_expense
|
accounts_query=accounts_query,
|
||||||
|
conditions=conditions,
|
||||||
),
|
),
|
||||||
self.filters,
|
self.filters,
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
@@ -414,7 +428,7 @@ class PartyLedgerSummaryReport(object):
|
|||||||
elif gle.party:
|
elif gle.party:
|
||||||
parties.setdefault(gle.party, 0)
|
parties.setdefault(gle.party, 0)
|
||||||
parties[gle.party] += gle.get(reverse_dr_or_cr) - gle.get(invoice_dr_or_cr)
|
parties[gle.party] += gle.get(reverse_dr_or_cr) - gle.get(invoice_dr_or_cr)
|
||||||
elif frappe.get_cached_value("Account", gle.account, "account_type") == income_or_expense:
|
elif frappe.get_cached_value("Account", gle.account, "account_type") == account_type:
|
||||||
accounts.setdefault(gle.account, 0)
|
accounts.setdefault(gle.account, 0)
|
||||||
accounts[gle.account] += gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr)
|
accounts[gle.account] += gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -211,7 +211,13 @@ def get_data(
|
|||||||
ignore_accumulated_values_for_fy,
|
ignore_accumulated_values_for_fy,
|
||||||
)
|
)
|
||||||
accumulate_values_into_parents(accounts, accounts_by_name, period_list)
|
accumulate_values_into_parents(accounts, accounts_by_name, period_list)
|
||||||
out = prepare_data(accounts, balance_must_be, period_list, company_currency)
|
out = prepare_data(
|
||||||
|
accounts,
|
||||||
|
balance_must_be,
|
||||||
|
period_list,
|
||||||
|
company_currency,
|
||||||
|
accumulated_values=filters.accumulated_values,
|
||||||
|
)
|
||||||
out = filter_out_zero_value_rows(out, parent_children_map)
|
out = filter_out_zero_value_rows(out, parent_children_map)
|
||||||
|
|
||||||
if out and total:
|
if out and total:
|
||||||
@@ -270,7 +276,7 @@ def accumulate_values_into_parents(accounts, accounts_by_name, period_list):
|
|||||||
) + d.get("opening_balance", 0.0)
|
) + d.get("opening_balance", 0.0)
|
||||||
|
|
||||||
|
|
||||||
def prepare_data(accounts, balance_must_be, period_list, company_currency):
|
def prepare_data(accounts, balance_must_be, period_list, company_currency, accumulated_values):
|
||||||
data = []
|
data = []
|
||||||
year_start_date = period_list[0]["year_start_date"].strftime("%Y-%m-%d")
|
year_start_date = period_list[0]["year_start_date"].strftime("%Y-%m-%d")
|
||||||
year_end_date = period_list[-1]["year_end_date"].strftime("%Y-%m-%d")
|
year_end_date = period_list[-1]["year_end_date"].strftime("%Y-%m-%d")
|
||||||
@@ -310,6 +316,12 @@ def prepare_data(accounts, balance_must_be, period_list, company_currency):
|
|||||||
has_value = True
|
has_value = True
|
||||||
total += flt(row[period.key])
|
total += flt(row[period.key])
|
||||||
|
|
||||||
|
if accumulated_values:
|
||||||
|
# when 'accumulated_values' is enabled, periods have running balance.
|
||||||
|
# so, last period will have the net amount.
|
||||||
|
row["has_value"] = has_value
|
||||||
|
row["total"] = flt(d.get(period_list[-1].key, 0.0), 3)
|
||||||
|
else:
|
||||||
row["has_value"] = has_value
|
row["has_value"] = has_value
|
||||||
row["total"] = total
|
row["total"] = total
|
||||||
data.append(row)
|
data.append(row)
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ frappe.query_reports["General Ledger"] = {
|
|||||||
frappe.query_report.set_filter_value('group_by', "Group by Voucher (Consolidated)");
|
frappe.query_report.set_filter_value('group_by', "Group by Voucher (Consolidated)");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"against_voucher_no",
|
||||||
|
"label": __("Against Voucher No"),
|
||||||
|
"fieldtype": "Data",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldtype": "Break",
|
"fieldtype": "Break",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ 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_link, against, is_opening, creation {select_fields}
|
against_link, against, is_opening, creation {select_fields}
|
||||||
@@ -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("against_voucher_no"):
|
||||||
|
conditions.append("against_voucher=%(against_voucher_no)s")
|
||||||
|
|
||||||
if filters.get("voucher_no_not_in"):
|
if filters.get("voucher_no_not_in"):
|
||||||
conditions.append("voucher_no not in %(voucher_no_not_in)s")
|
conditions.append("voucher_no not in %(voucher_no_not_in)s")
|
||||||
|
|
||||||
@@ -609,6 +612,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",
|
||||||
|
|||||||
@@ -66,11 +66,11 @@ def execute(filters=None):
|
|||||||
currency = filters.presentation_currency or frappe.get_cached_value(
|
currency = filters.presentation_currency or frappe.get_cached_value(
|
||||||
"Company", filters.company, "default_currency"
|
"Company", filters.company, "default_currency"
|
||||||
)
|
)
|
||||||
report_summary = get_report_summary(
|
report_summary, primitive_summary = get_report_summary(
|
||||||
period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters
|
period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters
|
||||||
)
|
)
|
||||||
|
|
||||||
return columns, data, None, chart, report_summary
|
return columns, data, None, chart, report_summary, primitive_summary
|
||||||
|
|
||||||
|
|
||||||
def get_report_summary(
|
def get_report_summary(
|
||||||
@@ -82,6 +82,17 @@ def get_report_summary(
|
|||||||
if filters.get("accumulated_in_group_company"):
|
if filters.get("accumulated_in_group_company"):
|
||||||
period_list = get_filtered_list_for_consolidated_report(filters, period_list)
|
period_list = get_filtered_list_for_consolidated_report(filters, period_list)
|
||||||
|
|
||||||
|
if filters.accumulated_values:
|
||||||
|
# when 'accumulated_values' is enabled, periods have running balance.
|
||||||
|
# so, last period will have the net amount.
|
||||||
|
key = period_list[-1].key
|
||||||
|
if income:
|
||||||
|
net_income = income[-2].get(key)
|
||||||
|
if expense:
|
||||||
|
net_expense = expense[-2].get(key)
|
||||||
|
if net_profit_loss:
|
||||||
|
net_profit = net_profit_loss.get(key)
|
||||||
|
else:
|
||||||
for period in period_list:
|
for period in period_list:
|
||||||
key = period if consolidated else period.key
|
key = period if consolidated else period.key
|
||||||
if income:
|
if income:
|
||||||
@@ -112,7 +123,7 @@ def get_report_summary(
|
|||||||
"datatype": "Currency",
|
"datatype": "Currency",
|
||||||
"currency": currency,
|
"currency": currency,
|
||||||
},
|
},
|
||||||
]
|
], net_profit
|
||||||
|
|
||||||
|
|
||||||
def get_net_profit_loss(income, expense, period_list, company, currency=None, consolidated=False):
|
def get_net_profit_loss(income, expense, period_list, company, currency=None, consolidated=False):
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# MIT License. See license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
from frappe.utils import add_days, getdate, today
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
from erpnext.accounts.report.financial_statements import get_period_list
|
||||||
|
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import execute
|
||||||
|
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||||
|
|
||||||
|
|
||||||
|
class TestProfitAndLossStatement(AccountsTestMixin, FrappeTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.create_company()
|
||||||
|
self.create_customer()
|
||||||
|
self.create_item()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
frappe.db.rollback()
|
||||||
|
|
||||||
|
def create_sales_invoice(self, qty=1, rate=150, no_payment_schedule=False, do_not_submit=False):
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
si = create_sales_invoice(
|
||||||
|
item=self.item,
|
||||||
|
company=self.company,
|
||||||
|
customer=self.customer,
|
||||||
|
debit_to=self.debit_to,
|
||||||
|
posting_date=today(),
|
||||||
|
parent_cost_center=self.cost_center,
|
||||||
|
cost_center=self.cost_center,
|
||||||
|
rate=rate,
|
||||||
|
price_list_rate=rate,
|
||||||
|
qty=qty,
|
||||||
|
do_not_save=1,
|
||||||
|
)
|
||||||
|
si = si.save()
|
||||||
|
if not do_not_submit:
|
||||||
|
si = si.submit()
|
||||||
|
return si
|
||||||
|
|
||||||
|
def get_fiscal_year(self):
|
||||||
|
active_fy = frappe.db.get_all(
|
||||||
|
"Fiscal Year",
|
||||||
|
filters={"disabled": 0, "year_start_date": ("<=", today()), "year_end_date": (">=", today())},
|
||||||
|
)[0]
|
||||||
|
return frappe.get_doc("Fiscal Year", active_fy.name)
|
||||||
|
|
||||||
|
def get_report_filters(self):
|
||||||
|
fy = self.get_fiscal_year()
|
||||||
|
return frappe._dict(
|
||||||
|
company=self.company,
|
||||||
|
from_fiscal_year=fy.name,
|
||||||
|
to_fiscal_year=fy.name,
|
||||||
|
period_start_date=fy.year_start_date,
|
||||||
|
period_end_date=fy.year_end_date,
|
||||||
|
filter_based_on="Fiscal Year",
|
||||||
|
periodicity="Monthly",
|
||||||
|
accumulated_vallues=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_profit_and_loss_output_and_summary(self):
|
||||||
|
si = self.create_sales_invoice(qty=1, rate=150)
|
||||||
|
|
||||||
|
filters = self.get_report_filters()
|
||||||
|
period_list = get_period_list(
|
||||||
|
filters.from_fiscal_year,
|
||||||
|
filters.to_fiscal_year,
|
||||||
|
filters.period_start_date,
|
||||||
|
filters.period_end_date,
|
||||||
|
filters.filter_based_on,
|
||||||
|
filters.periodicity,
|
||||||
|
company=filters.company,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = execute(filters)[1]
|
||||||
|
current_period = [x for x in period_list if x.from_date <= getdate() and x.to_date >= getdate()][
|
||||||
|
0
|
||||||
|
]
|
||||||
|
current_period_key = current_period.key
|
||||||
|
without_current_period = [x for x in period_list if x.key != current_period.key]
|
||||||
|
# all period except current period(whence invoice was posted), should be '0'
|
||||||
|
for acc in result:
|
||||||
|
if acc:
|
||||||
|
with self.subTest(acc=acc):
|
||||||
|
for period in without_current_period:
|
||||||
|
self.assertEqual(acc[period.key], 0)
|
||||||
|
|
||||||
|
for acc in result:
|
||||||
|
if acc:
|
||||||
|
with self.subTest(current_period_key=current_period_key):
|
||||||
|
self.assertEqual(acc[current_period_key], 150)
|
||||||
|
self.assertEqual(acc["total"], 150)
|
||||||
@@ -117,8 +117,3 @@ frappe.query_reports["Profitability Analysis"] = {
|
|||||||
"parent_field": "parent_account",
|
"parent_field": "parent_account",
|
||||||
"initial_depth": 3
|
"initial_depth": 3
|
||||||
}
|
}
|
||||||
|
|
||||||
erpnext.dimension_filters.forEach((dimension) => {
|
|
||||||
frappe.query_reports["Profitability Analysis"].filters[1].options.push(dimension["document_type"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|||||||
@@ -251,6 +251,7 @@ def get_journal_entries(filters, args):
|
|||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
(je.voucher_type == "Journal Entry")
|
(je.voucher_type == "Journal Entry")
|
||||||
|
& (je.docstatus == 1)
|
||||||
& (journal_account.party == filters.get(args.party))
|
& (journal_account.party == filters.get(args.party))
|
||||||
& (journal_account.account.isin(args.party_account))
|
& (journal_account.account.isin(args.party_account))
|
||||||
)
|
)
|
||||||
@@ -281,7 +282,9 @@ def get_payment_entries(filters, args):
|
|||||||
pe.cost_center,
|
pe.cost_center,
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
(pe.party == filters.get(args.party)) & (pe[args.account_fieldname].isin(args.party_account))
|
(pe.docstatus == 1)
|
||||||
|
& (pe.party == filters.get(args.party))
|
||||||
|
& (pe[args.account_fieldname].isin(args.party_account))
|
||||||
)
|
)
|
||||||
.orderby(pe.posting_date, pe.name, order=Order.desc)
|
.orderby(pe.posting_date, pe.name, order=Order.desc)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1116,12 +1116,16 @@ def get_companies():
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_children(doctype, parent, company, is_root=False):
|
def get_children(doctype, parent, company, is_root=False, include_disabled=False):
|
||||||
|
if isinstance(include_disabled, str):
|
||||||
|
include_disabled = frappe.json.loads(include_disabled)
|
||||||
from erpnext.accounts.report.financial_statements import sort_accounts
|
from erpnext.accounts.report.financial_statements import sort_accounts
|
||||||
|
|
||||||
parent_fieldname = "parent_" + doctype.lower().replace(" ", "_")
|
parent_fieldname = "parent_" + doctype.lower().replace(" ", "_")
|
||||||
fields = ["name as value", "is_group as expandable"]
|
fields = ["name as value", "is_group as expandable"]
|
||||||
filters = [["docstatus", "<", 2]]
|
filters = [["docstatus", "<", 2]]
|
||||||
|
if frappe.db.has_column(doctype, "disabled") and not include_disabled:
|
||||||
|
filters.append(["disabled", "=", False])
|
||||||
|
|
||||||
filters.append(['ifnull(`{0}`,"")'.format(parent_fieldname), "=", "" if is_root else parent])
|
filters.append(['ifnull(`{0}`,"")'.format(parent_fieldname), "=", "" if is_root else parent])
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"label": "Profit and Loss"
|
"label": "Profit and Loss"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"content": "[{\"id\":\"MmUf9abwxg\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"id\":\"VVvJ1lUcfc\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Outgoing Bills\",\"col\":3}},{\"id\":\"Vlj2FZtlHV\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Incoming Bills\",\"col\":3}},{\"id\":\"VVVjQVAhPf\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Incoming Payment\",\"col\":3}},{\"id\":\"DySNdlysIW\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Outgoing Payment\",\"col\":3}},{\"id\":\"i0EtSjDAXq\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"X78jcbq1u3\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"vikWSkNm6_\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"pMywM0nhlj\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"id\":\"_pRdD6kqUG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"G984SgVRJN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"id\":\"1ArNvt9qhz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"id\":\"F9f4I1viNr\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"id\":\"4IBBOIxfqW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"id\":\"El2anpPaFY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"id\":\"1nwcM9upJo\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"id\":\"OF9WOi1Ppc\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"iAwpe-Chra\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Accounting\",\"col\":3}},{\"id\":\"B7-uxs8tkU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"id\":\"DnNtsmxpty\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"id\":\"xOHTyD8b5l\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"id\":\"_Cb7C8XdJJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"id\":\"p7NY6MHe2Y\",\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"id\":\"KlqilF5R_V\",\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"id\":\"jTUy8LB0uw\",\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"id\":\"Wn2lhs7WLn\",\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"id\":\"PAQMqqNkBM\",\"type\":\"card\",\"data\":{\"card_name\":\"Bank Statement\",\"col\":4}},{\"id\":\"Q_hBCnSeJY\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"3AK1Zf0oew\",\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}},{\"id\":\"kxhoaiqdLq\",\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"id\":\"q0MAlU2j_Z\",\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"id\":\"ptm7T6Hwu-\",\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"id\":\"OX7lZHbiTr\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
|
"content": "[{\"id\":\"MmUf9abwxg\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"id\":\"VVvJ1lUcfc\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Outgoing Bills\",\"col\":3}},{\"id\":\"Vlj2FZtlHV\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Incoming Bills\",\"col\":3}},{\"id\":\"VVVjQVAhPf\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Incoming Payment\",\"col\":3}},{\"id\":\"DySNdlysIW\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Outgoing Payment\",\"col\":3}},{\"id\":\"i0EtSjDAXq\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"X78jcbq1u3\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"vikWSkNm6_\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"pMywM0nhlj\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"id\":\"_pRdD6kqUG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"G984SgVRJN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"id\":\"1ArNvt9qhz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"id\":\"F9f4I1viNr\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"id\":\"4IBBOIxfqW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"id\":\"El2anpPaFY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"id\":\"1nwcM9upJo\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"id\":\"OF9WOi1Ppc\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"iAwpe-Chra\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Accounting\",\"col\":3}},{\"id\":\"B7-uxs8tkU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"id\":\"DnNtsmxpty\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"id\":\"xOHTyD8b5l\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"id\":\"_Cb7C8XdJJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"id\":\"p7NY6MHe2Y\",\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"id\":\"KlqilF5R_V\",\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"id\":\"jTUy8LB0uw\",\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"id\":\"Wn2lhs7WLn\",\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"id\":\"PAQMqqNkBM\",\"type\":\"card\",\"data\":{\"card_name\":\"Banking\",\"col\":4}},{\"id\":\"Q_hBCnSeJY\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"3AK1Zf0oew\",\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}},{\"id\":\"kxhoaiqdLq\",\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"id\":\"q0MAlU2j_Z\",\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"id\":\"ptm7T6Hwu-\",\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"id\":\"OX7lZHbiTr\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
|
||||||
"creation": "2020-03-02 15:41:59.515192",
|
"creation": "2020-03-02 15:41:59.515192",
|
||||||
"custom_blocks": [],
|
"custom_blocks": [],
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
@@ -652,14 +652,6 @@
|
|||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Bank Statement",
|
|
||||||
"link_count": 0,
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Card Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"dependencies": "",
|
"dependencies": "",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@@ -1059,9 +1051,83 @@
|
|||||||
"link_type": "Report",
|
"link_type": "Report",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Banking",
|
||||||
|
"link_count": 6,
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Card Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Bank",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Bank",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Bank Account",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Bank Account",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Bank Clearance",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Bank Clearance",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Bank Reconciliation Tool",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Bank Reconciliation Tool",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dependencies": "GL Entry",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 1,
|
||||||
|
"label": "Bank Reconciliation Statement",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Bank Reconciliation Statement",
|
||||||
|
"link_type": "Report",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": "Plaid Settings",
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": "Plaid Settings",
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-08-10 17:41:14.059005",
|
"modified": "2024-01-02 15:21:09.895531",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounting",
|
"name": "Accounting",
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.utils import now_datetime
|
||||||
|
|
||||||
|
|
||||||
class AssetActivity(Document):
|
class AssetActivity(Document):
|
||||||
@@ -30,5 +31,6 @@ def add_asset_activity(asset, subject):
|
|||||||
"asset": asset,
|
"asset": asset,
|
||||||
"subject": subject,
|
"subject": subject,
|
||||||
"user": frappe.session.user,
|
"user": frappe.session.user,
|
||||||
|
"date": now_datetime(),
|
||||||
}
|
}
|
||||||
).insert(ignore_permissions=True, ignore_links=True)
|
).insert(ignore_permissions=True, ignore_links=True)
|
||||||
|
|||||||
@@ -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"):
|
||||||
|
|||||||
@@ -452,6 +452,7 @@ class PurchaseOrder(BuyingController):
|
|||||||
self.update_requested_qty()
|
self.update_requested_qty()
|
||||||
self.update_ordered_qty()
|
self.update_ordered_qty()
|
||||||
self.update_reserved_qty_for_subcontract()
|
self.update_reserved_qty_for_subcontract()
|
||||||
|
self.update_subcontracting_order_status()
|
||||||
self.notify_update()
|
self.notify_update()
|
||||||
clear_doctype_notifications(self)
|
clear_doctype_notifications(self)
|
||||||
|
|
||||||
@@ -630,6 +631,17 @@ class PurchaseOrder(BuyingController):
|
|||||||
if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):
|
if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):
|
||||||
make_subcontracting_order(self.name, save=True, notify=True)
|
make_subcontracting_order(self.name, save=True, notify=True)
|
||||||
|
|
||||||
|
def update_subcontracting_order_status(self):
|
||||||
|
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
|
||||||
|
update_subcontracting_order_status as update_sco_status,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.is_subcontracted and not self.is_old_subcontracting_flow:
|
||||||
|
sco = frappe.db.get_value("Subcontracting Order", {"purchase_order": self.name, "docstatus": 1})
|
||||||
|
|
||||||
|
if sco:
|
||||||
|
update_sco_status(sco, "Closed" if self.status == "Closed" else None)
|
||||||
|
|
||||||
|
|
||||||
def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
|
def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
|
||||||
"""get last purchase rate for an item"""
|
"""get last purchase rate for an item"""
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -129,6 +129,17 @@ class AccountsController(TransactionBase):
|
|||||||
if self.doctype in relevant_docs:
|
if self.doctype in relevant_docs:
|
||||||
self.set_payment_schedule()
|
self.set_payment_schedule()
|
||||||
|
|
||||||
|
def remove_bundle_for_non_stock_invoices(self):
|
||||||
|
has_sabb = False
|
||||||
|
if self.doctype in ("Sales Invoice", "Purchase Invoice") and not self.update_stock:
|
||||||
|
for item in self.get("items"):
|
||||||
|
if item.serial_and_batch_bundle:
|
||||||
|
item.serial_and_batch_bundle = None
|
||||||
|
has_sabb = True
|
||||||
|
|
||||||
|
if has_sabb:
|
||||||
|
self.remove_serial_and_batch_bundle()
|
||||||
|
|
||||||
def ensure_supplier_is_not_blocked(self):
|
def ensure_supplier_is_not_blocked(self):
|
||||||
is_supplier_payment = self.doctype == "Payment Entry" and self.party_type == "Supplier"
|
is_supplier_payment = self.doctype == "Payment Entry" and self.party_type == "Supplier"
|
||||||
is_buying_invoice = self.doctype in ["Purchase Invoice", "Purchase Order"]
|
is_buying_invoice = self.doctype in ["Purchase Invoice", "Purchase Order"]
|
||||||
@@ -156,6 +167,9 @@ class AccountsController(TransactionBase):
|
|||||||
if self.get("_action") and self._action != "update_after_submit":
|
if self.get("_action") and self._action != "update_after_submit":
|
||||||
self.set_missing_values(for_validate=True)
|
self.set_missing_values(for_validate=True)
|
||||||
|
|
||||||
|
if self.get("_action") == "submit":
|
||||||
|
self.remove_bundle_for_non_stock_invoices()
|
||||||
|
|
||||||
self.ensure_supplier_is_not_blocked()
|
self.ensure_supplier_is_not_blocked()
|
||||||
|
|
||||||
self.validate_date_with_fiscal_year()
|
self.validate_date_with_fiscal_year()
|
||||||
@@ -225,6 +239,11 @@ class AccountsController(TransactionBase):
|
|||||||
apply_pricing_rule_on_transaction(self)
|
apply_pricing_rule_on_transaction(self)
|
||||||
|
|
||||||
self.set_total_in_words()
|
self.set_total_in_words()
|
||||||
|
self.set_default_letter_head()
|
||||||
|
|
||||||
|
def set_default_letter_head(self):
|
||||||
|
if hasattr(self, "letter_head") and not self.letter_head:
|
||||||
|
self.letter_head = frappe.db.get_value("Company", self.company, "default_letter_head")
|
||||||
|
|
||||||
def init_internal_values(self):
|
def init_internal_values(self):
|
||||||
# init all the internal values as 0 on sa
|
# init all the internal values as 0 on sa
|
||||||
@@ -556,18 +575,12 @@ class AccountsController(TransactionBase):
|
|||||||
validate_due_date(
|
validate_due_date(
|
||||||
self.posting_date,
|
self.posting_date,
|
||||||
self.due_date,
|
self.due_date,
|
||||||
"Customer",
|
|
||||||
self.customer,
|
|
||||||
self.company,
|
|
||||||
self.payment_terms_template,
|
self.payment_terms_template,
|
||||||
)
|
)
|
||||||
elif self.doctype == "Purchase Invoice":
|
elif self.doctype == "Purchase Invoice":
|
||||||
validate_due_date(
|
validate_due_date(
|
||||||
self.bill_date or self.posting_date,
|
self.bill_date or self.posting_date,
|
||||||
self.due_date,
|
self.due_date,
|
||||||
"Supplier",
|
|
||||||
self.supplier,
|
|
||||||
self.company,
|
|
||||||
self.bill_date,
|
self.bill_date,
|
||||||
self.payment_terms_template,
|
self.payment_terms_template,
|
||||||
)
|
)
|
||||||
@@ -874,6 +887,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(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -916,7 +930,7 @@ class AccountsController(TransactionBase):
|
|||||||
# Update details in transaction currency
|
# Update details in transaction currency
|
||||||
gl_dict.update(
|
gl_dict.update(
|
||||||
{
|
{
|
||||||
"transaction_currency": self.get("currency") or self.company_currency,
|
"transaction_currency": args.get("currency") or self.get("currency") or self.company_currency,
|
||||||
"transaction_exchange_rate": self.get("conversion_rate", 1),
|
"transaction_exchange_rate": self.get("conversion_rate", 1),
|
||||||
"debit_in_transaction_currency": self.get_value_in_transaction_currency(
|
"debit_in_transaction_currency": self.get_value_in_transaction_currency(
|
||||||
account_currency, args, "debit"
|
account_currency, args, "debit"
|
||||||
@@ -927,13 +941,38 @@ class AccountsController(TransactionBase):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not args.get("against_voucher_type") and self.get("against_voucher_type"):
|
||||||
|
gl_dict.update({"against_voucher_type": self.get("against_voucher_type")})
|
||||||
|
|
||||||
|
if not args.get("against_voucher") and self.get("against_voucher"):
|
||||||
|
gl_dict.update({"against_voucher": self.get("against_voucher")})
|
||||||
|
|
||||||
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 == args.get("currency") or self.get("currency"):
|
||||||
return args.get(field + "_in_account_currency")
|
return args.get(field + "_in_account_currency")
|
||||||
else:
|
else:
|
||||||
return flt(args.get(field, 0) / self.get("conversion_rate", 1))
|
return flt(args.get(field, 0) / (args.get("conversion_rate") or self.get("conversion_rate", 1)))
|
||||||
|
|
||||||
def validate_qty_is_not_zero(self):
|
def validate_qty_is_not_zero(self):
|
||||||
if self.doctype == "Purchase Receipt":
|
if self.doctype == "Purchase Receipt":
|
||||||
@@ -2412,6 +2451,7 @@ def validate_taxes_and_charges(tax):
|
|||||||
|
|
||||||
def validate_account_head(idx, account, company, context=""):
|
def validate_account_head(idx, account, company, context=""):
|
||||||
account_company = frappe.get_cached_value("Account", account, "company")
|
account_company = frappe.get_cached_value("Account", account, "company")
|
||||||
|
is_group = frappe.get_cached_value("Account", account, "is_group")
|
||||||
|
|
||||||
if account_company != company:
|
if account_company != company:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
@@ -2421,6 +2461,12 @@ def validate_account_head(idx, account, company, context=""):
|
|||||||
title=_("Invalid Account"),
|
title=_("Invalid Account"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if is_group:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row {0}: Account {1} is a Group Account").format(idx, frappe.bold(account)),
|
||||||
|
title=_("Invalid Account"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate_cost_center(tax, doc):
|
def validate_cost_center(tax, doc):
|
||||||
if not tax.cost_center:
|
if not tax.cost_center:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -562,6 +562,7 @@ 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
|
||||||
|
|
||||||
|
if source_doc.item_code:
|
||||||
item_details = frappe.get_cached_value(
|
item_details = frappe.get_cached_value(
|
||||||
"Item", source_doc.item_code, ["has_batch_no", "has_serial_no"], as_dict=1
|
"Item", source_doc.item_code, ["has_batch_no", "has_serial_no"], as_dict=1
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -432,13 +432,18 @@ 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 frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
|
||||||
|
continue
|
||||||
|
|
||||||
if not self.get("return_against") or (
|
if not self.get("return_against") or (
|
||||||
get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
|
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 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,
|
||||||
|
|||||||
@@ -1,228 +0,0 @@
|
|||||||
{
|
|
||||||
"charts": [],
|
|
||||||
"content": "[{\"id\":\"e88ADOJ7WC\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Integrations</b></span>\",\"col\":12}},{\"id\":\"pZEYOOCdB0\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Browse Apps\",\"col\":3}},{\"id\":\"St7AHbhVOr\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"nu4oSjH5Rd\",\"type\":\"card\",\"data\":{\"card_name\":\"Authentication\",\"col\":4}},{\"id\":\"G0tyx9WOfm\",\"type\":\"card\",\"data\":{\"card_name\":\"Backup\",\"col\":4}},{\"id\":\"nG8cdkpzoc\",\"type\":\"card\",\"data\":{\"card_name\":\"Google Services\",\"col\":4}},{\"id\":\"4hwuQn6E95\",\"type\":\"card\",\"data\":{\"card_name\":\"Communication Channels\",\"col\":4}},{\"id\":\"sEGAzTJRmq\",\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}}]",
|
|
||||||
"creation": "2020-08-20 19:30:48.138801",
|
|
||||||
"custom_blocks": [],
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "Workspace",
|
|
||||||
"for_user": "",
|
|
||||||
"hide_custom": 0,
|
|
||||||
"icon": "integration",
|
|
||||||
"idx": 0,
|
|
||||||
"is_hidden": 0,
|
|
||||||
"label": "ERPNext Integrations",
|
|
||||||
"links": [
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Backup",
|
|
||||||
"link_count": 3,
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Card Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Dropbox Settings",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Dropbox Settings",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "S3 Backup Settings",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "S3 Backup Settings",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Google Drive",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Google Drive",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Authentication",
|
|
||||||
"link_count": 4,
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Card Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Social Login",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Social Login Key",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "LDAP Settings",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "LDAP Settings",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "OAuth Client",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "OAuth Client",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "OAuth Provider Settings",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "OAuth Provider Settings",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Communication Channels",
|
|
||||||
"link_count": 3,
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Card Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Webhook",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Webhook",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "SMS Settings",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "SMS Settings",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Slack Webhook URL",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Slack Webhook URL",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Google Services",
|
|
||||||
"link_count": 4,
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Card Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Google Settings",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Google Settings",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Google Contacts",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Google Contacts",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Google Calendar",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Google Calendar",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Google Drive",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Google Drive",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Payments",
|
|
||||||
"link_count": 3,
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Card Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hidden": 0,
|
|
||||||
"is_query_report": 0,
|
|
||||||
"label": "Plaid Settings",
|
|
||||||
"link_count": 0,
|
|
||||||
"link_to": "Plaid Settings",
|
|
||||||
"link_type": "DocType",
|
|
||||||
"onboard": 0,
|
|
||||||
"type": "Link"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"modified": "2023-10-31 19:57:32.748726",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "ERPNext Integrations",
|
|
||||||
"name": "ERPNext Integrations",
|
|
||||||
"number_cards": [],
|
|
||||||
"owner": "Administrator",
|
|
||||||
"parent_page": "",
|
|
||||||
"public": 1,
|
|
||||||
"quick_lists": [],
|
|
||||||
"restrict_to_domain": "",
|
|
||||||
"roles": [],
|
|
||||||
"sequence_id": 21.0,
|
|
||||||
"shortcuts": [
|
|
||||||
{
|
|
||||||
"color": "Grey",
|
|
||||||
"doc_view": "List",
|
|
||||||
"label": "Browse Apps",
|
|
||||||
"type": "URL",
|
|
||||||
"url": "https://frappecloud.com/marketplace"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "ERPNext Integrations"
|
|
||||||
}
|
|
||||||
@@ -37,6 +37,7 @@ welcome_email = "erpnext.setup.utils.welcome_email"
|
|||||||
# setup wizard
|
# setup wizard
|
||||||
setup_wizard_requires = "assets/erpnext/js/setup_wizard.js"
|
setup_wizard_requires = "assets/erpnext/js/setup_wizard.js"
|
||||||
setup_wizard_stages = "erpnext.setup.setup_wizard.setup_wizard.get_setup_stages"
|
setup_wizard_stages = "erpnext.setup.setup_wizard.setup_wizard.get_setup_stages"
|
||||||
|
setup_wizard_complete = "erpnext.setup.setup_wizard.setup_wizard.setup_demo"
|
||||||
setup_wizard_test = "erpnext.setup.setup_wizard.test_setup_wizard.run_setup_wizard_test"
|
setup_wizard_test = "erpnext.setup.setup_wizard.test_setup_wizard.run_setup_wizard_test"
|
||||||
|
|
||||||
before_install = [
|
before_install = [
|
||||||
@@ -257,11 +258,6 @@ standard_portal_menu_items = [
|
|||||||
{"title": "Appointment Booking", "route": "/book_appointment"},
|
{"title": "Appointment Booking", "route": "/book_appointment"},
|
||||||
]
|
]
|
||||||
|
|
||||||
default_roles = [
|
|
||||||
{"role": "Customer", "doctype": "Contact", "email_field": "email_id"},
|
|
||||||
{"role": "Supplier", "doctype": "Contact", "email_field": "email_id"},
|
|
||||||
]
|
|
||||||
|
|
||||||
sounds = [
|
sounds = [
|
||||||
{"name": "incoming-call", "src": "/assets/erpnext/sounds/incoming-call.mp3", "volume": 0.2},
|
{"name": "incoming-call", "src": "/assets/erpnext/sounds/incoming-call.mp3", "volume": 0.2},
|
||||||
{"name": "call-disconnect", "src": "/assets/erpnext/sounds/call-disconnect.mp3", "volume": 0.2},
|
{"name": "call-disconnect", "src": "/assets/erpnext/sounds/call-disconnect.mp3", "volume": 0.2},
|
||||||
|
|||||||
@@ -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(
|
||||||
@@ -1488,3 +1491,47 @@ def make_variant_bom(source_name, bom_no, item, variant_items, target_doc=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
||||||
|
def get_op_cost_from_sub_assemblies(bom_no, op_cost=0):
|
||||||
|
# Get operating cost from sub-assemblies
|
||||||
|
|
||||||
|
bom_items = frappe.get_all(
|
||||||
|
"BOM Item", filters={"parent": bom_no, "docstatus": 1}, fields=["bom_no"], order_by="idx asc"
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in bom_items:
|
||||||
|
if not row.bom_no:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if cost := frappe.get_cached_value("BOM", row.bom_no, "operating_cost_per_bom_quantity"):
|
||||||
|
op_cost += flt(cost)
|
||||||
|
get_op_cost_from_sub_assemblies(row.bom_no, op_cost)
|
||||||
|
|
||||||
|
return op_cost
|
||||||
|
|
||||||
|
|
||||||
|
def get_scrap_items_from_sub_assemblies(bom_no, company, qty, scrap_items=None):
|
||||||
|
if not scrap_items:
|
||||||
|
scrap_items = {}
|
||||||
|
|
||||||
|
bom_items = frappe.get_all(
|
||||||
|
"BOM Item",
|
||||||
|
filters={"parent": bom_no, "docstatus": 1},
|
||||||
|
fields=["bom_no", "qty"],
|
||||||
|
order_by="idx asc",
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in bom_items:
|
||||||
|
if not row.bom_no:
|
||||||
|
continue
|
||||||
|
|
||||||
|
qty = flt(row.qty) * flt(qty)
|
||||||
|
items = get_bom_items_as_dict(
|
||||||
|
row.bom_no, company, qty=qty, fetch_exploded=0, fetch_scrap_items=1
|
||||||
|
)
|
||||||
|
scrap_items.update(items)
|
||||||
|
|
||||||
|
get_scrap_items_from_sub_assemblies(row.bom_no, company, qty, scrap_items)
|
||||||
|
|
||||||
|
return scrap_items
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ class BOMCreator(Document):
|
|||||||
|
|
||||||
frappe.msgprint(_("BOMs created successfully"))
|
frappe.msgprint(_("BOMs created successfully"))
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback = frappe.get_traceback()
|
traceback = frappe.get_traceback(with_context=True)
|
||||||
self.db_set(
|
self.db_set(
|
||||||
{
|
{
|
||||||
"status": "Failed",
|
"status": "Failed",
|
||||||
|
|||||||
@@ -37,7 +37,8 @@
|
|||||||
"oldfieldname": "item_code",
|
"oldfieldname": "item_code",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Item",
|
"options": "Item",
|
||||||
"read_only": 1
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "item_name",
|
"fieldname": "item_name",
|
||||||
@@ -170,7 +171,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-14 18:35:40.856895",
|
"modified": "2024-01-02 13:49:36.211586",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM Explosion Item",
|
"name": "BOM Explosion Item",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -273,35 +273,39 @@ class JobCard(Document):
|
|||||||
|
|
||||||
def has_overlap(self, production_capacity, time_logs):
|
def has_overlap(self, production_capacity, time_logs):
|
||||||
overlap = False
|
overlap = False
|
||||||
if production_capacity == 1 and len(time_logs) > 0:
|
if production_capacity == 1 and len(time_logs) >= 1:
|
||||||
return True
|
return True
|
||||||
|
if not len(time_logs):
|
||||||
|
return False
|
||||||
|
|
||||||
# Check overlap exists or not between the overlapping time logs with the current Job Card
|
# sorting overlapping job cards as per from_time
|
||||||
for row in time_logs:
|
time_logs = sorted(time_logs, key=lambda x: x.get("from_time"))
|
||||||
count = 1
|
# alloted_capacity has key number starting from 1. Key number will increment by 1 if non sequential job card found
|
||||||
for next_row in time_logs:
|
# if key number reaches/crosses to production_capacity means capacity is full and overlap error generated
|
||||||
if row.name == next_row.name:
|
# this will store last to_time of sequential job cards
|
||||||
continue
|
alloted_capacity = {1: time_logs[0]["to_time"]}
|
||||||
|
# flag for sequential Job card found
|
||||||
if (
|
sequential_job_card_found = False
|
||||||
(
|
for i in range(1, len(time_logs)):
|
||||||
get_datetime(next_row.from_time) >= get_datetime(row.from_time)
|
# scanning for all Existing keys
|
||||||
and get_datetime(next_row.from_time) <= get_datetime(row.to_time)
|
for key in alloted_capacity.keys():
|
||||||
)
|
# if current Job Card from time is greater than last to_time in that key means these job card are sequential
|
||||||
or (
|
if alloted_capacity[key] <= time_logs[i]["from_time"]:
|
||||||
get_datetime(next_row.to_time) >= get_datetime(row.from_time)
|
# So update key's value with last to_time
|
||||||
and get_datetime(next_row.to_time) <= get_datetime(row.to_time)
|
alloted_capacity[key] = time_logs[i]["to_time"]
|
||||||
)
|
# flag is true as we get sequential Job Card for that key
|
||||||
or (
|
sequential_job_card_found = True
|
||||||
get_datetime(next_row.from_time) <= get_datetime(row.from_time)
|
# Immediately break so that job card to time is not added with any other key except this
|
||||||
and get_datetime(next_row.to_time) >= get_datetime(row.to_time)
|
break
|
||||||
)
|
# if sequential job card not found above means it is overlapping so increment key number to alloted_capacity
|
||||||
):
|
if not sequential_job_card_found:
|
||||||
count += 1
|
# increment key number
|
||||||
|
key = key + 1
|
||||||
if count > production_capacity:
|
# for that key last to time is assigned.
|
||||||
|
alloted_capacity[key] = time_logs[i]["to_time"]
|
||||||
|
if len(alloted_capacity) >= production_capacity:
|
||||||
|
# if number of keys greater or equal to production caoacity means full capacity is utilized and we should throw overlap error
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return overlap
|
return overlap
|
||||||
|
|
||||||
def get_time_logs(self, args, doctype, check_next_available_slot=False):
|
def get_time_logs(self, args, doctype, check_next_available_slot=False):
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
"job_card_excess_transfer",
|
"job_card_excess_transfer",
|
||||||
"other_settings_section",
|
"other_settings_section",
|
||||||
"update_bom_costs_automatically",
|
"update_bom_costs_automatically",
|
||||||
|
"set_op_cost_and_scrape_from_sub_assemblies",
|
||||||
"column_break_23",
|
"column_break_23",
|
||||||
"make_serial_no_batch_from_work_order"
|
"make_serial_no_batch_from_work_order"
|
||||||
],
|
],
|
||||||
@@ -194,13 +195,20 @@
|
|||||||
"fieldname": "job_card_excess_transfer",
|
"fieldname": "job_card_excess_transfer",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Allow Excess Material Transfer"
|
"label": "Allow Excess Material Transfer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "In the case of 'Use Multi-Level BOM' in a work order, if the user wishes to add sub-assembly costs to Finished Goods items without using a job card as well the scrap items, then this option needs to be enable.",
|
||||||
|
"fieldname": "set_op_cost_and_scrape_from_sub_assemblies",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Set Operating Cost / Scrape Items From Sub-assemblies"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-wrench",
|
"icon": "icon-wrench",
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-09-13 22:09:09.401559",
|
"modified": "2023-12-28 16:37:44.874096",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Manufacturing Settings",
|
"name": "Manufacturing Settings",
|
||||||
@@ -216,5 +224,6 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,7 @@ class ManufacturingSettings(Document):
|
|||||||
mins_between_operations: DF.Int
|
mins_between_operations: DF.Int
|
||||||
overproduction_percentage_for_sales_order: DF.Percent
|
overproduction_percentage_for_sales_order: DF.Percent
|
||||||
overproduction_percentage_for_work_order: DF.Percent
|
overproduction_percentage_for_work_order: DF.Percent
|
||||||
|
set_op_cost_and_scrape_from_sub_assemblies: DF.Check
|
||||||
update_bom_costs_automatically: DF.Check
|
update_bom_costs_automatically: DF.Check
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1211,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"]
|
||||||
|
|
||||||
@@ -1220,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"]:
|
||||||
@@ -1559,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,
|
||||||
|
|||||||
@@ -1499,6 +1499,29 @@ class TestProductionPlan(FrappeTestCase):
|
|||||||
after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
|
after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
|
||||||
self.assertAlmostEqual(after_qty, before_qty)
|
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):
|
||||||
"""
|
"""
|
||||||
@@ -1579,6 +1602,10 @@ def make_bom(**args):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if args.operating_cost_per_bom_quantity:
|
||||||
|
bom.fg_based_operating_cost = 1
|
||||||
|
bom.operating_cost_per_bom_quantity = args.operating_cost_per_bom_quantity
|
||||||
|
|
||||||
for item in args.raw_materials:
|
for item in args.raw_materials:
|
||||||
item_doc = frappe.get_doc("Item", item)
|
item_doc = frappe.get_doc("Item", item)
|
||||||
bom.append(
|
bom.append(
|
||||||
|
|||||||
@@ -921,11 +921,9 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
"Test RM Item 2 for Scrap Item Test",
|
"Test RM Item 2 for Scrap Item Test",
|
||||||
]
|
]
|
||||||
|
|
||||||
from_time = add_days(now(), -1)
|
|
||||||
job_cards = frappe.get_all(
|
job_cards = frappe.get_all(
|
||||||
"Job Card Time Log",
|
"Job Card Time Log",
|
||||||
fields=["distinct parent as name", "docstatus"],
|
fields=["distinct parent as name", "docstatus"],
|
||||||
filters={"from_time": (">", from_time)},
|
|
||||||
order_by="creation asc",
|
order_by="creation asc",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1732,6 +1730,93 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
job_card2.time_logs = []
|
job_card2.time_logs = []
|
||||||
job_card2.save()
|
job_card2.save()
|
||||||
|
|
||||||
|
def test_op_cost_and_scrap_based_on_sub_assemblies(self):
|
||||||
|
# Make Sub Assembly BOM 1
|
||||||
|
|
||||||
|
frappe.db.set_single_value(
|
||||||
|
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies", 1
|
||||||
|
)
|
||||||
|
|
||||||
|
items = {
|
||||||
|
"Test Final FG Item": 0,
|
||||||
|
"Test Final SF Item 1": 0,
|
||||||
|
"Test Final SF Item 2": 0,
|
||||||
|
"Test Final RM Item 1": 100,
|
||||||
|
"Test Final RM Item 2": 200,
|
||||||
|
"Test Final Scrap Item 1": 50,
|
||||||
|
"Test Final Scrap Item 2": 60,
|
||||||
|
}
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if not frappe.db.exists("Item", item):
|
||||||
|
item_properties = {"is_stock_item": 1, "valuation_rate": items[item]}
|
||||||
|
|
||||||
|
make_item(item_code=item, properties=item_properties),
|
||||||
|
|
||||||
|
prepare_boms_for_sub_assembly_test()
|
||||||
|
|
||||||
|
wo_order = make_wo_order_test_record(
|
||||||
|
production_item="Test Final FG Item",
|
||||||
|
qty=10,
|
||||||
|
use_multi_level_bom=1,
|
||||||
|
skip_transfer=1,
|
||||||
|
from_wip_warehouse=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
se_doc = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10))
|
||||||
|
se_doc.save()
|
||||||
|
|
||||||
|
self.assertTrue(se_doc.additional_costs)
|
||||||
|
scrap_items = []
|
||||||
|
for item in se_doc.items:
|
||||||
|
if item.is_scrap_item:
|
||||||
|
scrap_items.append(item.item_code)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(scrap_items), sorted(["Test Final Scrap Item 1", "Test Final Scrap Item 2"])
|
||||||
|
)
|
||||||
|
for row in se_doc.additional_costs:
|
||||||
|
self.assertEqual(row.amount, 3000)
|
||||||
|
|
||||||
|
frappe.db.set_single_value(
|
||||||
|
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies", 0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_boms_for_sub_assembly_test():
|
||||||
|
if not frappe.db.exists("BOM", {"item": "Test Final SF Item 1"}):
|
||||||
|
bom = make_bom(
|
||||||
|
item="Test Final SF Item 1",
|
||||||
|
source_warehouse="Stores - _TC",
|
||||||
|
raw_materials=["Test Final RM Item 1"],
|
||||||
|
operating_cost_per_bom_quantity=100,
|
||||||
|
do_not_submit=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
bom.append("scrap_items", {"item_code": "Test Final Scrap Item 1", "qty": 1})
|
||||||
|
|
||||||
|
bom.submit()
|
||||||
|
|
||||||
|
if not frappe.db.exists("BOM", {"item": "Test Final SF Item 2"}):
|
||||||
|
bom = make_bom(
|
||||||
|
item="Test Final SF Item 2",
|
||||||
|
source_warehouse="Stores - _TC",
|
||||||
|
raw_materials=["Test Final RM Item 2"],
|
||||||
|
operating_cost_per_bom_quantity=200,
|
||||||
|
do_not_submit=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
bom.append("scrap_items", {"item_code": "Test Final Scrap Item 2", "qty": 1})
|
||||||
|
|
||||||
|
bom.submit()
|
||||||
|
|
||||||
|
if not frappe.db.exists("BOM", {"item": "Test Final FG Item"}):
|
||||||
|
bom = make_bom(
|
||||||
|
item="Test Final FG Item",
|
||||||
|
source_warehouse="Stores - _TC",
|
||||||
|
raw_materials=["Test Final SF Item 1", "Test Final SF Item 2"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def prepare_data_for_workstation_type_check():
|
def prepare_data_for_workstation_type_check():
|
||||||
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
|
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
|
||||||
@@ -1978,6 +2063,7 @@ def make_wo_order_test_record(**args):
|
|||||||
wo_order.sales_order = args.sales_order or None
|
wo_order.sales_order = args.sales_order or None
|
||||||
wo_order.planned_start_date = args.planned_start_date or now()
|
wo_order.planned_start_date = args.planned_start_date or now()
|
||||||
wo_order.transfer_material_against = args.transfer_material_against or "Work Order"
|
wo_order.transfer_material_against = args.transfer_material_against or "Work Order"
|
||||||
|
wo_order.from_wip_warehouse = args.from_wip_warehouse or 0
|
||||||
|
|
||||||
if args.source_warehouse:
|
if args.source_warehouse:
|
||||||
for item in wo_order.get("required_items"):
|
for item in wo_order.get("required_items"):
|
||||||
|
|||||||
@@ -352,7 +352,9 @@ 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
|
||||||
erpnext.patches.v15_0.create_advance_payment_status
|
erpnext.patches.v15_0.create_advance_payment_status
|
||||||
# below migration patch should always run last
|
# below migration patch should always run last
|
||||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||||
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index
|
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
|
||||||
|
erpnext.patches.v14_0.set_maintain_stock_for_bom_item
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ def execute():
|
|||||||
# append list of new department for each company
|
# append list of new department for each company
|
||||||
comp_dict[company.name][department.name] = copy_doc.name
|
comp_dict[company.name][department.name] = copy_doc.name
|
||||||
|
|
||||||
rebuild_tree("Department", "parent_department")
|
rebuild_tree("Department")
|
||||||
doctypes = ["Asset", "Employee", "Payroll Entry", "Staffing Plan", "Job Opening"]
|
doctypes = ["Asset", "Employee", "Payroll Entry", "Staffing Plan", "Job Opening"]
|
||||||
|
|
||||||
for d in doctypes:
|
for d in doctypes:
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ def execute():
|
|||||||
except frappe.DuplicateEntryError:
|
except frappe.DuplicateEntryError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
rebuild_tree("Location", "parent_location")
|
rebuild_tree("Location")
|
||||||
|
|
||||||
|
|
||||||
def get_parent_warehouse_name(warehouse):
|
def get_parent_warehouse_name(warehouse):
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ from frappe.utils.nestedset import rebuild_tree
|
|||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
frappe.reload_doc("setup", "doctype", "company")
|
frappe.reload_doc("setup", "doctype", "company")
|
||||||
rebuild_tree("Company", "parent_company")
|
rebuild_tree("Company")
|
||||||
|
|||||||
@@ -41,4 +41,4 @@ def build_tree():
|
|||||||
}
|
}
|
||||||
).insert(ignore_permissions=True)
|
).insert(ignore_permissions=True)
|
||||||
|
|
||||||
rebuild_tree("Supplier Group", "parent_supplier_group")
|
rebuild_tree("Supplier Group")
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ def execute():
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
rebuild_tree("Department", "parent_department")
|
rebuild_tree("Department")
|
||||||
|
|||||||
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
|
||||||
|
"""
|
||||||
|
)
|
||||||
@@ -4,5 +4,5 @@ import frappe
|
|||||||
def execute():
|
def execute():
|
||||||
subscription = frappe.qb.DocType("Subscription")
|
subscription = frappe.qb.DocType("Subscription")
|
||||||
frappe.qb.update(subscription).set(
|
frappe.qb.update(subscription).set(
|
||||||
subscription.generate_invoice_at, "Beginning of the currency subscription period"
|
subscription.generate_invoice_at, "Beginning of the current subscription period"
|
||||||
).where(subscription.generate_invoice_at_period_start == 1).run()
|
).where(subscription.generate_invoice_at_period_start == 1).run()
|
||||||
|
|||||||
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()
|
||||||
@@ -454,7 +454,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
item.weight_uom = '';
|
item.weight_uom = '';
|
||||||
item.conversion_factor = 0;
|
item.conversion_factor = 0;
|
||||||
|
|
||||||
if(['Sales Invoice'].includes(this.frm.doc.doctype)) {
|
if(['Sales Invoice', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
|
||||||
update_stock = cint(me.frm.doc.update_stock);
|
update_stock = cint(me.frm.doc.update_stock);
|
||||||
show_batch_dialog = update_stock;
|
show_batch_dialog = update_stock;
|
||||||
|
|
||||||
@@ -545,7 +545,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
},
|
},
|
||||||
() => me.toggle_conversion_factor(item),
|
() => me.toggle_conversion_factor(item),
|
||||||
() => {
|
() => {
|
||||||
if (show_batch_dialog)
|
if (show_batch_dialog && !frappe.flags.trigger_from_barcode_scanner)
|
||||||
return frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
|
return frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
if (r.message &&
|
if (r.message &&
|
||||||
@@ -715,6 +715,11 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
}
|
}
|
||||||
|
|
||||||
on_submit() {
|
on_submit() {
|
||||||
|
if (in_list(["Purchase Invoice", "Sales Invoice"], this.frm.doc.doctype)
|
||||||
|
&& !this.frm.doc.update_stock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.refresh_serial_batch_bundle_field();
|
this.refresh_serial_batch_bundle_field();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1234,6 +1239,20 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sync_bundle_data() {
|
||||||
|
let doctypes = ["Sales Invoice", "Purchase Invoice", "Delivery Note", "Purchase Receipt"];
|
||||||
|
|
||||||
|
if (this.frm.is_new() && doctypes.includes(this.frm.doc.doctype)) {
|
||||||
|
const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
|
||||||
|
barcode_scanner.sync_bundle_data();
|
||||||
|
barcode_scanner.remove_item_from_localstorage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
before_save(doc) {
|
||||||
|
this.sync_bundle_data();
|
||||||
|
}
|
||||||
|
|
||||||
service_start_date(frm, cdt, cdn) {
|
service_start_date(frm, cdt, cdn) {
|
||||||
var child = locals[cdt][cdn];
|
var child = locals[cdt][cdn];
|
||||||
|
|
||||||
@@ -1571,6 +1590,18 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
return item_list;
|
return item_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
items_delete() {
|
||||||
|
this.update_localstorage_scanned_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
update_localstorage_scanned_data() {
|
||||||
|
let doctypes = ["Sales Invoice", "Purchase Invoice", "Delivery Note", "Purchase Receipt"];
|
||||||
|
if (this.frm.is_new() && doctypes.includes(this.frm.doc.doctype)) {
|
||||||
|
const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
|
||||||
|
barcode_scanner.update_localstorage_scanned_data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_set_values_for_item_list(children) {
|
_set_values_for_item_list(children) {
|
||||||
const items_rule_dict = {};
|
const items_rule_dict = {};
|
||||||
|
|
||||||
|
|||||||
@@ -843,7 +843,7 @@ erpnext.utils.map_current_doc = function(opts) {
|
|||||||
freeze_message: __("Mapping {0} ...", [opts.source_doctype]),
|
freeze_message: __("Mapping {0} ...", [opts.source_doctype]),
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(!r.exc) {
|
if(!r.exc) {
|
||||||
var doc = frappe.model.sync(r.message);
|
frappe.model.sync(r.message);
|
||||||
cur_frm.dirty();
|
cur_frm.dirty();
|
||||||
cur_frm.refresh();
|
cur_frm.refresh();
|
||||||
}
|
}
|
||||||
@@ -865,11 +865,20 @@ erpnext.utils.map_current_doc = function(opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (opts.source_doctype) {
|
if (opts.source_doctype) {
|
||||||
|
let data_fields = [];
|
||||||
|
if(opts.source_doctype == "Purchase Receipt") {
|
||||||
|
data_fields.push({
|
||||||
|
fieldname: 'merge_taxes',
|
||||||
|
fieldtype: 'Check',
|
||||||
|
label: __('Merge taxes from multiple documents'),
|
||||||
|
});
|
||||||
|
}
|
||||||
const d = new frappe.ui.form.MultiSelectDialog({
|
const d = new frappe.ui.form.MultiSelectDialog({
|
||||||
doctype: opts.source_doctype,
|
doctype: opts.source_doctype,
|
||||||
target: opts.target,
|
target: opts.target,
|
||||||
date_field: opts.date_field || undefined,
|
date_field: opts.date_field || undefined,
|
||||||
setters: opts.setters,
|
setters: opts.setters,
|
||||||
|
data_fields: data_fields,
|
||||||
get_query: opts.get_query,
|
get_query: opts.get_query,
|
||||||
add_filters_group: 1,
|
add_filters_group: 1,
|
||||||
allow_child_item_selection: opts.allow_child_item_selection,
|
allow_child_item_selection: opts.allow_child_item_selection,
|
||||||
@@ -883,7 +892,7 @@ erpnext.utils.map_current_doc = function(opts) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
opts.source_name = values;
|
opts.source_name = values;
|
||||||
if (opts.allow_child_item_selection) {
|
if (opts.allow_child_item_selection || opts.source_doctype == "Purchase Receipt") {
|
||||||
// args contains filtered child docnames
|
// args contains filtered child docnames
|
||||||
opts.args = args;
|
opts.args = args;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
this.scan_barcode_field = this.frm.fields_dict[this.scan_field_name];
|
this.scan_barcode_field = this.frm.fields_dict[this.scan_field_name];
|
||||||
|
|
||||||
this.barcode_field = opts.barcode_field || "barcode";
|
this.barcode_field = opts.barcode_field || "barcode";
|
||||||
this.serial_no_field = opts.serial_no_field || "serial_no";
|
|
||||||
this.batch_no_field = opts.batch_no_field || "batch_no";
|
|
||||||
this.uom_field = opts.uom_field || "uom";
|
this.uom_field = opts.uom_field || "uom";
|
||||||
this.qty_field = opts.qty_field || "qty";
|
this.qty_field = opts.qty_field || "qty";
|
||||||
// field name on row which defines max quantity to be scanned e.g. picklist
|
// field name on row which defines max quantity to be scanned e.g. picklist
|
||||||
@@ -84,6 +82,7 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
update_table(data) {
|
update_table(data) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
|
let cur_grid = this.frm.fields_dict[this.items_table_name].grid;
|
||||||
|
frappe.flags.trigger_from_barcode_scanner = true;
|
||||||
|
|
||||||
const {item_code, barcode, batch_no, serial_no, uom} = data;
|
const {item_code, barcode, batch_no, serial_no, uom} = data;
|
||||||
|
|
||||||
@@ -106,50 +105,38 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
this.frm.has_items = false;
|
this.frm.has_items = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.is_duplicate_serial_no(row, serial_no)) {
|
if (serial_no && this.is_duplicate_serial_no(row, item_code, serial_no)) {
|
||||||
this.clean_up();
|
this.clean_up();
|
||||||
reject();
|
reject();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
frappe.run_serially([
|
frappe.run_serially([
|
||||||
() => this.set_selector_trigger_flag(data),
|
() => this.set_serial_and_batch(row, item_code, serial_no, batch_no),
|
||||||
() => this.set_serial_no(row, serial_no),
|
|
||||||
() => this.set_batch_no(row, batch_no),
|
|
||||||
() => this.set_barcode(row, barcode),
|
() => this.set_barcode(row, barcode),
|
||||||
() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
|
() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
|
||||||
this.show_scan_message(row.idx, row.item_code, qty);
|
this.show_scan_message(row.idx, row.item_code, qty);
|
||||||
}),
|
}),
|
||||||
() => this.set_barcode_uom(row, uom),
|
() => this.set_barcode_uom(row, uom),
|
||||||
() => this.clean_up(),
|
() => this.clean_up(),
|
||||||
() => this.revert_selector_flag(),
|
() => resolve(row),
|
||||||
() => resolve(row)
|
() => {
|
||||||
|
if (row.serial_and_batch_bundle && !this.frm.is_new()) {
|
||||||
|
this.frm.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.flags.trigger_from_barcode_scanner = false;
|
||||||
|
}
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// batch and serial selector is reduandant when all info can be added by scan
|
|
||||||
// this flag on item row is used by transaction.js to avoid triggering selector
|
|
||||||
set_selector_trigger_flag(data) {
|
|
||||||
const {has_batch_no, has_serial_no} = data;
|
|
||||||
|
|
||||||
const require_selecting_batch = has_batch_no;
|
|
||||||
const require_selecting_serial = has_serial_no;
|
|
||||||
|
|
||||||
if (!(require_selecting_batch || require_selecting_serial)) {
|
|
||||||
frappe.flags.hide_serial_batch_dialog = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
revert_selector_flag() {
|
|
||||||
frappe.flags.hide_serial_batch_dialog = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
set_item(row, item_code, barcode, batch_no, serial_no) {
|
set_item(row, item_code, barcode, batch_no, serial_no) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const increment = async (value = 1) => {
|
const increment = async (value = 1) => {
|
||||||
const item_data = {item_code: item_code};
|
const item_data = {item_code: item_code};
|
||||||
item_data[this.qty_field] = Number((row[this.qty_field] || 0)) + Number(value);
|
item_data[this.qty_field] = Number((row[this.qty_field] || 0)) + Number(value);
|
||||||
|
frappe.flags.trigger_from_barcode_scanner = true;
|
||||||
await frappe.model.set_value(row.doctype, row.name, item_data);
|
await frappe.model.set_value(row.doctype, row.name, item_data);
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
@@ -158,8 +145,6 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
frappe.prompt(__("Please enter quantity for item {0}", [item_code]), ({value}) => {
|
frappe.prompt(__("Please enter quantity for item {0}", [item_code]), ({value}) => {
|
||||||
increment(value).then((value) => resolve(value));
|
increment(value).then((value) => resolve(value));
|
||||||
});
|
});
|
||||||
} else if (this.frm.has_items) {
|
|
||||||
this.prepare_item_for_scan(row, item_code, barcode, batch_no, serial_no);
|
|
||||||
} else {
|
} else {
|
||||||
increment().then((value) => resolve(value));
|
increment().then((value) => resolve(value));
|
||||||
}
|
}
|
||||||
@@ -182,9 +167,8 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
frappe.model.set_value(row.doctype, row.name, item_data);
|
frappe.model.set_value(row.doctype, row.name, item_data);
|
||||||
|
|
||||||
frappe.run_serially([
|
frappe.run_serially([
|
||||||
() => this.set_batch_no(row, this.dialog.get_value("batch_no")),
|
|
||||||
() => this.set_barcode(row, this.dialog.get_value("barcode")),
|
() => this.set_barcode(row, this.dialog.get_value("barcode")),
|
||||||
() => this.set_serial_no(row, this.dialog.get_value("serial_no")),
|
() => this.set_serial_and_batch(row, item_code, this.dialog.get_value("serial_no"), this.dialog.get_value("batch_no")),
|
||||||
() => this.add_child_for_remaining_qty(row),
|
() => this.add_child_for_remaining_qty(row),
|
||||||
() => this.clean_up()
|
() => this.clean_up()
|
||||||
]);
|
]);
|
||||||
@@ -338,18 +322,136 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async set_serial_no(row, serial_no) {
|
async set_serial_and_batch(row, item_code, serial_no, batch_no) {
|
||||||
if (serial_no && frappe.meta.has_field(row.doctype, this.serial_no_field)) {
|
if (this.frm.is_new() || !row.serial_and_batch_bundle) {
|
||||||
const existing_serial_nos = row[this.serial_no_field];
|
this.set_bundle_in_localstorage(row, item_code, serial_no, batch_no);
|
||||||
let new_serial_nos = "";
|
} else if(row.serial_and_batch_bundle) {
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.update_serial_or_batch",
|
||||||
|
args: {
|
||||||
|
bundle_id: row.serial_and_batch_bundle,
|
||||||
|
serial_no: serial_no,
|
||||||
|
batch_no: batch_no,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!!existing_serial_nos) {
|
get_key_for_localstorage() {
|
||||||
new_serial_nos = existing_serial_nos + "\n" + serial_no;
|
let parts = this.frm.doc.name.split("-");
|
||||||
} else {
|
return parts[parts.length - 1] + this.frm.doc.doctype;
|
||||||
new_serial_nos = serial_no;
|
|
||||||
}
|
}
|
||||||
await frappe.model.set_value(row.doctype, row.name, this.serial_no_field, new_serial_nos);
|
|
||||||
|
update_localstorage_scanned_data() {
|
||||||
|
let docname = this.frm.doc.name
|
||||||
|
if (localStorage[docname]) {
|
||||||
|
let items = JSON.parse(localStorage[docname]);
|
||||||
|
let existing_items = this.frm.doc.items.map(d => d.item_code);
|
||||||
|
if (!existing_items.length) {
|
||||||
|
localStorage.removeItem(docname);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let item_code in items) {
|
||||||
|
if (!existing_items.includes(item_code)) {
|
||||||
|
delete items[item_code];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage[docname] = JSON.stringify(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async set_bundle_in_localstorage(row, item_code, serial_no, batch_no) {
|
||||||
|
let docname = this.frm.doc.name
|
||||||
|
|
||||||
|
let entries = JSON.parse(localStorage.getItem(docname));
|
||||||
|
if (!entries) {
|
||||||
|
entries = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = item_code;
|
||||||
|
if (!entries[key]) {
|
||||||
|
entries[key] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let existing_row = [];
|
||||||
|
if (!serial_no && batch_no) {
|
||||||
|
existing_row = entries[key].filter((e) => e.batch_no === batch_no);
|
||||||
|
if (existing_row.length) {
|
||||||
|
existing_row[0].qty += 1;
|
||||||
|
}
|
||||||
|
} else if (serial_no) {
|
||||||
|
existing_row = entries[key].filter((e) => e.serial_no === serial_no);
|
||||||
|
if (existing_row.length) {
|
||||||
|
frappe.throw(__("Serial No {0} has already scanned.", [serial_no]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!existing_row.length) {
|
||||||
|
entries[key].push({
|
||||||
|
"serial_no": serial_no,
|
||||||
|
"batch_no": batch_no,
|
||||||
|
"qty": 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem(docname, JSON.stringify(entries));
|
||||||
|
|
||||||
|
// Auto remove from localstorage after 1 hour
|
||||||
|
setTimeout(() => {
|
||||||
|
localStorage.removeItem(docname);
|
||||||
|
}, 3600000)
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_item_from_localstorage() {
|
||||||
|
let docname = this.frm.doc.name;
|
||||||
|
if (localStorage[docname]) {
|
||||||
|
localStorage.removeItem(docname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sync_bundle_data() {
|
||||||
|
let docname = this.frm.doc.name;
|
||||||
|
|
||||||
|
if (localStorage[docname]) {
|
||||||
|
let entries = JSON.parse(localStorage[docname]);
|
||||||
|
if (entries) {
|
||||||
|
for (let entry in entries) {
|
||||||
|
let row = this.frm.doc.items.filter((item) => {
|
||||||
|
if (item.item_code === entry) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})[0];
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
this.create_serial_and_batch_bundle(row, entries, entry)
|
||||||
|
.then(() => {
|
||||||
|
if (!entries) {
|
||||||
|
localStorage.removeItem(docname);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async create_serial_and_batch_bundle(row, entries, key) {
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.add_serial_batch_ledgers",
|
||||||
|
args: {
|
||||||
|
entries: entries[key],
|
||||||
|
child_row: row,
|
||||||
|
doc: this.frm.doc,
|
||||||
|
warehouse: row.warehouse,
|
||||||
|
do_not_save: 1
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
row.serial_and_batch_bundle = r.message.name;
|
||||||
|
delete entries[key];
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async set_barcode_uom(row, uom) {
|
async set_barcode_uom(row, uom) {
|
||||||
@@ -358,12 +460,6 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async set_batch_no(row, batch_no) {
|
|
||||||
if (batch_no && frappe.meta.has_field(row.doctype, this.batch_no_field)) {
|
|
||||||
await frappe.model.set_value(row.doctype, row.name, this.batch_no_field, batch_no);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async set_barcode(row, barcode) {
|
async set_barcode(row, barcode) {
|
||||||
if (barcode && frappe.meta.has_field(row.doctype, this.barcode_field)) {
|
if (barcode && frappe.meta.has_field(row.doctype, this.barcode_field)) {
|
||||||
await frappe.model.set_value(row.doctype, row.name, this.barcode_field, barcode);
|
await frappe.model.set_value(row.doctype, row.name, this.barcode_field, barcode);
|
||||||
@@ -379,13 +475,52 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is_duplicate_serial_no(row, serial_no) {
|
is_duplicate_serial_no(row, item_code, serial_no) {
|
||||||
const is_duplicate = row[this.serial_no_field]?.includes(serial_no);
|
if (this.frm.is_new() || !row.serial_and_batch_bundle) {
|
||||||
|
let is_duplicate = this.check_duplicate_serial_no_in_localstorage(item_code, serial_no);
|
||||||
if (is_duplicate) {
|
if (is_duplicate) {
|
||||||
this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
|
this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
|
||||||
}
|
}
|
||||||
|
|
||||||
return is_duplicate;
|
return is_duplicate;
|
||||||
|
} else if (row.serial_and_batch_bundle) {
|
||||||
|
this.check_duplicate_serial_no_in_db(row, serial_no, (r) => {
|
||||||
|
if (r.message) {
|
||||||
|
this.show_alert(__("Serial No {0} is already added", [serial_no]), "orange");
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.message;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async check_duplicate_serial_no_in_db(row, serial_no, response) {
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.is_duplicate_serial_no",
|
||||||
|
args: {
|
||||||
|
serial_no: serial_no,
|
||||||
|
bundle_id: row.serial_and_batch_bundle
|
||||||
|
},
|
||||||
|
callback(r) {
|
||||||
|
response(r);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
check_duplicate_serial_no_in_localstorage(item_code, serial_no) {
|
||||||
|
let docname = this.frm.doc.name
|
||||||
|
let entries = JSON.parse(localStorage.getItem(docname));
|
||||||
|
|
||||||
|
if (!entries) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let existing_row = [];
|
||||||
|
if (entries[item_code]) {
|
||||||
|
existing_row = entries[item_code].filter((e) => e.serial_no === serial_no);
|
||||||
|
}
|
||||||
|
|
||||||
|
return existing_row.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
get_row_to_modify_on_scan(item_code, batch_no, uom, barcode) {
|
get_row_to_modify_on_scan(item_code, batch_no, uom, barcode) {
|
||||||
|
|||||||
@@ -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"));
|
||||||
|
|||||||
@@ -337,16 +337,18 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get_auto_data() {
|
get_auto_data() {
|
||||||
|
let { qty, based_on } = this.dialog.get_values();
|
||||||
|
|
||||||
if (this.item.serial_and_batch_bundle || this.item.rejected_serial_and_batch_bundle) {
|
if (this.item.serial_and_batch_bundle || this.item.rejected_serial_and_batch_bundle) {
|
||||||
|
if (qty === this.qty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.item.serial_no || this.item.batch_no) {
|
if (this.item.serial_no || this.item.batch_no) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { qty, based_on } = this.dialog.get_values();
|
|
||||||
|
|
||||||
if (!based_on) {
|
if (!based_on) {
|
||||||
based_on = 'FIFO';
|
based_on = 'FIFO';
|
||||||
}
|
}
|
||||||
@@ -502,6 +504,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
|||||||
|
|
||||||
set_data(data) {
|
set_data(data) {
|
||||||
data.forEach(d => {
|
data.forEach(d => {
|
||||||
|
d.qty = Math.abs(d.qty);
|
||||||
this.dialog.fields_dict.entries.df.data.push(d);
|
this.dialog.fields_dict.entries.df.data.push(d);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ def update_itemised_tax_data(doc):
|
|||||||
# dont even bother checking in item tax template as it contains both input and output accounts - double the tax rate
|
# dont even bother checking in item tax template as it contains both input and output accounts - double the tax rate
|
||||||
item_code = row.item_code or row.item_name
|
item_code = row.item_code or row.item_name
|
||||||
if itemised_tax.get(item_code):
|
if itemised_tax.get(item_code):
|
||||||
for tax in itemised_tax.get(row.item_code).values():
|
for tax in itemised_tax.get(item_code).values():
|
||||||
_tax_rate = flt(tax.get("tax_rate", 0), row.precision("tax_rate"))
|
_tax_rate = flt(tax.get("tax_rate", 0), row.precision("tax_rate"))
|
||||||
tax_amount += flt((row.net_amount * _tax_rate) / 100, row.precision("tax_amount"))
|
tax_amount += flt((row.net_amount * _tax_rate) / 100, row.precision("tax_amount"))
|
||||||
tax_rate += _tax_rate
|
tax_rate += _tax_rate
|
||||||
|
|||||||
@@ -448,7 +448,6 @@
|
|||||||
"report_hide": 1
|
"report_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
|
||||||
"fieldname": "credit_limits",
|
"fieldname": "credit_limits",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Credit Limit",
|
"label": "Credit Limit",
|
||||||
@@ -584,7 +583,7 @@
|
|||||||
"link_fieldname": "party"
|
"link_fieldname": "party"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-10-19 16:56:27.327035",
|
"modified": "2023-12-28 13:15:36.298369",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Customer",
|
"name": "Customer",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user