mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-11 09:22:30 +00:00
Compare commits
21 Commits
test
...
mihir-kand
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c003d911f1 | ||
|
|
2d3190effb | ||
|
|
a68769565b | ||
|
|
19234cafbe | ||
|
|
09623d4c0c | ||
|
|
032a282f84 | ||
|
|
ca093177e0 | ||
|
|
ea3cf57042 | ||
|
|
06ffe52d6e | ||
|
|
c120cc7ed1 | ||
|
|
25be38e23c | ||
|
|
2a720e7008 | ||
|
|
f38eca9124 | ||
|
|
ad89f88c93 | ||
|
|
78f654765d | ||
|
|
231dd1856f | ||
|
|
da081254a6 | ||
|
|
c543d15f3c | ||
|
|
ddf0e35009 | ||
|
|
88b82383f5 | ||
|
|
c4155b6c81 |
70
.github/workflows/propogate-auto-merge.yml
vendored
Normal file
70
.github/workflows/propogate-auto-merge.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Propagate auto-merge label to backports
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
propagate-label:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- name: Detect backport and original PR (metadata)
|
||||
id: extract
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const base = pr.base.ref;
|
||||
if (!/^version-(15|16)-hotfix$/.test(base)) {
|
||||
core.setOutput('is_backport', 'false');
|
||||
return;
|
||||
}
|
||||
|
||||
const isMergify = pr.user && pr.user.login === 'mergify[bot]';
|
||||
const headRef = pr.head.ref || '';
|
||||
const match = headRef.match(/(?:^|\/)pr-(\d+)(?:-|$)/i);
|
||||
|
||||
if (isMergify && match) {
|
||||
const original = Number(match[1]);
|
||||
if (!Number.isNaN(original)) {
|
||||
core.setOutput('original_pr', String(original));
|
||||
core.setOutput('is_backport', 'true');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
core.setOutput('is_backport', 'false');
|
||||
|
||||
- name: Check original PR labels
|
||||
if: steps.extract.outputs.is_backport == 'true'
|
||||
id: check
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const prNumber = Number("${{ steps.extract.outputs.original_pr }}");
|
||||
|
||||
const { data } = await github.rest.issues.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
const labels = data.labels.map(l => l.name);
|
||||
core.setOutput("has_auto_merge", labels.includes("auto-merge backports"));
|
||||
|
||||
- name: Add auto-merge label to backport PR
|
||||
if: steps.check.outputs.has_auto_merge == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
labels: ["auto-merge"]
|
||||
});
|
||||
15
.mergify.yml
15
.mergify.yml
@@ -92,3 +92,18 @@ pull_request_rules:
|
||||
{{ title }} (#{{ number }})
|
||||
|
||||
{{ body }}
|
||||
- name: Auto merge backports (safe)
|
||||
conditions:
|
||||
- label=auto-merge
|
||||
- base~=^version-(15|16)-hotfix$
|
||||
- -conflict
|
||||
- status-success=Python Unit Tests (1)
|
||||
- status-success=Python Unit Tests (2)
|
||||
- status-success=Python Unit Tests (3)
|
||||
- status-success=Python Unit Tests (4)
|
||||
- status-success=linters
|
||||
- status-success=semgrep
|
||||
- status-success=Patch Test
|
||||
actions:
|
||||
merge:
|
||||
method: squash
|
||||
10
README.md
10
README.md
@@ -10,7 +10,7 @@
|
||||
|
||||
[](https://frappe.school)<br><br>
|
||||
[](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml)
|
||||
[](https://hub.docker.com/r/frappe/erpnext-worker)
|
||||
[](https://hub.docker.com/r/frappe/erpnext)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -88,14 +88,6 @@ See [Frappe Docker Documentation](https://github.com/frappe/frappe_docker) for f
|
||||
|
||||
> For Docker basics and best practices refer to Docker's [documentation](https://docs.docker.com)
|
||||
|
||||
#### Demo setup
|
||||
|
||||
The fastest way to try ERPNext is to play in a pre-configured sandbox, in your browser, click the button below:
|
||||
|
||||
<a href="https://labs.play-with-docker.com/?stack=https://raw.githubusercontent.com/frappe/frappe_docker/main/pwd.yml">
|
||||
<img src="https://raw.githubusercontent.com/play-with-docker/stacks/master/assets/images/button.png" alt="Try in PWD"/>
|
||||
</a>
|
||||
|
||||
### Try on your environment
|
||||
|
||||
> **⚠️ Disposable demo only**
|
||||
|
||||
@@ -34,6 +34,13 @@
|
||||
"account_number": "0430",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Anlagen im Bau": {
|
||||
"is_group": 1,
|
||||
"Andere Anlagen, Betriebs- und Geschäftsausstattung im Bau": {
|
||||
"account_number": "0498",
|
||||
"account_type": "Capital Work in Progress"
|
||||
}
|
||||
},
|
||||
"Accumulated Depreciation": {
|
||||
"account_type": "Accumulated Depreciation"
|
||||
}
|
||||
@@ -317,13 +324,21 @@
|
||||
"account_number": "3800",
|
||||
"account_type": "Expenses Included In Asset Valuation"
|
||||
},
|
||||
"Bestandsveränderungen Roh-, Hilfs- und Betriebsstoffe sowie bezogene Waren": {
|
||||
"account_number": "3960",
|
||||
"account_type": "Stock Adjustment"
|
||||
},
|
||||
"Herstellungskosten": {
|
||||
"account_number": "4996",
|
||||
"account_type": "Cost of Goods Sold"
|
||||
},
|
||||
"Anlagenabgänge Sachanlagen (Restbuchwert bei Buchverlust)": {
|
||||
"account_number": "2310",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Verluste aus dem Abgang von Gegenständen des Anlagevermögens": {
|
||||
"account_number": "2320",
|
||||
"account_type": "Stock Adjustment"
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Verwaltungskosten": {
|
||||
"account_number": "4997",
|
||||
@@ -340,7 +355,7 @@
|
||||
"is_group": 1,
|
||||
"Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": {
|
||||
"account_number": "4830",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Abschreibungen auf Gebäude": {
|
||||
"account_number": "4831",
|
||||
|
||||
@@ -664,6 +664,7 @@
|
||||
"fieldname": "total_billing_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Billing Amount",
|
||||
"options": "currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -1531,6 +1532,7 @@
|
||||
"fieldname": "amount_eligible_for_commission",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount Eligible for Commission",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -1639,7 +1641,7 @@
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-30 12:15:57.253316",
|
||||
"modified": "2026-05-01 02:37:30.580568",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
|
||||
@@ -680,10 +680,6 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
},
|
||||
|
||||
is_subcontracted: function (frm) {
|
||||
if (frm.doc.is_old_subcontracting_flow) {
|
||||
erpnext.buying.get_default_bom(frm);
|
||||
}
|
||||
|
||||
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted);
|
||||
},
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_bulk_edit": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-05-21 16:16:39",
|
||||
@@ -206,7 +207,6 @@
|
||||
"sender",
|
||||
"column_break_147",
|
||||
"inter_company_invoice_reference",
|
||||
"is_old_subcontracting_flow",
|
||||
"remarks",
|
||||
"connections_tab"
|
||||
],
|
||||
@@ -1456,14 +1456,6 @@
|
||||
"label": "Subscription",
|
||||
"options": "Subscription"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_old_subcontracting_flow",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Is Old Subcontracting Flow",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "payments_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
@@ -1703,7 +1695,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-30 12:16:40.157755",
|
||||
"modified": "2026-05-04 10:10:11.717131",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -129,7 +129,6 @@ class PurchaseInvoice(BuyingController):
|
||||
incoterm: DF.Link | None
|
||||
inter_company_invoice_reference: DF.Link | None
|
||||
is_internal_supplier: DF.Check
|
||||
is_old_subcontracting_flow: DF.Check
|
||||
is_opening: DF.Literal["No", "Yes"]
|
||||
is_paid: DF.Check
|
||||
is_return: DF.Check
|
||||
@@ -778,9 +777,6 @@ class PurchaseInvoice(BuyingController):
|
||||
self.make_bundle_using_old_serial_batch_fields()
|
||||
self.update_stock_ledger()
|
||||
|
||||
if self.is_old_subcontracting_flow:
|
||||
self.set_consumed_qty_in_subcontract_order()
|
||||
|
||||
# this sequence because outstanding may get -negative
|
||||
self.make_gl_entries()
|
||||
|
||||
@@ -1706,9 +1702,6 @@ class PurchaseInvoice(BuyingController):
|
||||
self.update_stock_ledger()
|
||||
self.delete_auto_created_batches()
|
||||
|
||||
if self.is_old_subcontracting_flow:
|
||||
self.set_consumed_qty_in_subcontract_order()
|
||||
|
||||
self.make_gl_entries_on_cancel()
|
||||
|
||||
if self.update_stock == 1:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_bulk_edit": 1,
|
||||
"autoname": "hash",
|
||||
"creation": "2013-05-22 12:43:10",
|
||||
"doctype": "DocType",
|
||||
@@ -99,7 +100,6 @@
|
||||
"service_end_date",
|
||||
"reference",
|
||||
"item_tax_rate",
|
||||
"bom",
|
||||
"include_exploded_items",
|
||||
"purchase_invoice_item",
|
||||
"col_break6",
|
||||
@@ -645,15 +645,6 @@
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "bom",
|
||||
"fieldtype": "Link",
|
||||
"label": "BOM",
|
||||
"options": "BOM",
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!parent.is_old_subcontracting_flow"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.is_subcontracted",
|
||||
@@ -1007,7 +998,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-07 15:40:45.687554",
|
||||
"modified": "2026-05-04 10:48:54.535480",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -26,7 +26,6 @@ class PurchaseInvoiceItem(Document):
|
||||
base_rate: DF.Currency
|
||||
base_rate_with_margin: DF.Currency
|
||||
batch_no: DF.Link | None
|
||||
bom: DF.Link | None
|
||||
brand: DF.Link | None
|
||||
conversion_factor: DF.Float
|
||||
cost_center: DF.Link | None
|
||||
|
||||
@@ -1150,6 +1150,7 @@
|
||||
"hide_seconds": 1,
|
||||
"label": "Rounding Adjustment",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -1162,6 +1163,7 @@
|
||||
"label": "Rounded Total",
|
||||
"oldfieldname": "rounded_total",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -2355,7 +2357,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2026-03-30 12:17:16.201016",
|
||||
"modified": "2026-05-01 02:37:29.742764",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -194,6 +194,9 @@ def reschedule_depreciation(asset_doc, notes, disposal_date=None):
|
||||
for row in asset_doc.get("finance_books"):
|
||||
current_schedule = get_asset_depr_schedule_doc(asset_doc.name, None, row.finance_book)
|
||||
|
||||
if disposal_date and flt(row.value_after_depreciation) <= flt(row.expected_value_after_useful_life):
|
||||
continue
|
||||
|
||||
if current_schedule:
|
||||
if current_schedule.docstatus == 1:
|
||||
new_schedule = frappe.copy_doc(current_schedule)
|
||||
|
||||
@@ -12,18 +12,6 @@ erpnext.buying.setup_buying_controller();
|
||||
|
||||
frappe.ui.form.on("Purchase Order", {
|
||||
setup: function (frm) {
|
||||
if (frm.doc.is_old_subcontracting_flow) {
|
||||
frm.set_query("reserve_warehouse", "supplied_items", function () {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
name: ["!=", frm.doc.supplier_warehouse],
|
||||
is_group: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
frm.set_indicator_formatter("item_code", function (doc) {
|
||||
let color;
|
||||
if (!doc.qty && frm.doc.has_unit_price_items) {
|
||||
@@ -72,18 +60,6 @@ frappe.ui.form.on("Purchase Order", {
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.is_old_subcontracting_flow) {
|
||||
frm.trigger("get_materials_from_supplier");
|
||||
|
||||
$("a.grey-link").each(function () {
|
||||
var id = $(this).children(":first-child").attr("data-label");
|
||||
if (id == "Duplicate") {
|
||||
$(this).remove();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus == 0) {
|
||||
erpnext.set_unit_price_items_note(frm);
|
||||
}
|
||||
@@ -179,7 +155,7 @@ frappe.ui.form.on("Purchase Order Item", {
|
||||
},
|
||||
|
||||
item_code: async function (frm, cdt, cdn) {
|
||||
if (frm.doc.is_subcontracted && !frm.doc.is_old_subcontracting_flow) {
|
||||
if (frm.doc.is_subcontracted) {
|
||||
var row = locals[cdt][cdn];
|
||||
|
||||
if (row.item_code && !row.fg_item) {
|
||||
@@ -227,7 +203,7 @@ frappe.ui.form.on("Purchase Order Item", {
|
||||
},
|
||||
|
||||
fg_item: async function (frm, cdt, cdn) {
|
||||
if (frm.doc.is_subcontracted && !frm.doc.is_old_subcontracting_flow) {
|
||||
if (frm.doc.is_subcontracted) {
|
||||
var row = locals[cdt][cdn];
|
||||
|
||||
if (row.fg_item) {
|
||||
@@ -248,7 +224,7 @@ frappe.ui.form.on("Purchase Order Item", {
|
||||
},
|
||||
|
||||
qty: async function (frm, cdt, cdn) {
|
||||
if (frm.doc.is_subcontracted && !frm.doc.is_old_subcontracting_flow) {
|
||||
if (frm.doc.is_subcontracted) {
|
||||
var row = locals[cdt][cdn];
|
||||
|
||||
if (row.fg_item) {
|
||||
@@ -383,26 +359,14 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
__("Create")
|
||||
);
|
||||
if (doc.is_subcontracted) {
|
||||
if (doc.is_old_subcontracting_flow) {
|
||||
if (me.has_unsupplied_items()) {
|
||||
this.frm.add_custom_button(
|
||||
__("Material to Supplier"),
|
||||
function () {
|
||||
me.make_stock_entry();
|
||||
},
|
||||
__("Transfer")
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (!doc.items.every((item) => item.qty == item.subcontracted_qty)) {
|
||||
this.frm.add_custom_button(
|
||||
__("Subcontracting Order"),
|
||||
() => {
|
||||
me.make_subcontracting_order();
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
if (!doc.items.every((item) => item.qty == item.subcontracted_qty)) {
|
||||
this.frm.add_custom_button(
|
||||
__("Subcontracting Order"),
|
||||
() => {
|
||||
me.make_subcontracting_order();
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -748,20 +712,6 @@ cur_frm.cscript.update_status = function (label, status) {
|
||||
});
|
||||
};
|
||||
|
||||
if (cur_frm.doc.is_old_subcontracting_flow) {
|
||||
cur_frm.fields_dict["items"].grid.get_field("bom").get_query = function (doc, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
return {
|
||||
filters: [
|
||||
["BOM", "item", "=", d.item_code],
|
||||
["BOM", "is_active", "=", "1"],
|
||||
["BOM", "docstatus", "=", "1"],
|
||||
["BOM", "company", "=", doc.company],
|
||||
],
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function set_schedule_date(frm) {
|
||||
if (frm.doc.schedule_date) {
|
||||
erpnext.utils.copy_value_in_all_rows(
|
||||
@@ -775,9 +725,3 @@ function set_schedule_date(frm) {
|
||||
}
|
||||
|
||||
frappe.provide("erpnext.buying");
|
||||
|
||||
frappe.ui.form.on("Purchase Order", "is_subcontracted", function (frm) {
|
||||
if (frm.doc.is_old_subcontracting_flow) {
|
||||
erpnext.buying.get_default_bom(frm);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_auto_repeat": 1,
|
||||
"allow_bulk_edit": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-05-21 16:16:39",
|
||||
@@ -164,7 +165,6 @@
|
||||
"mps",
|
||||
"is_internal_supplier",
|
||||
"inter_company_order_reference",
|
||||
"is_old_subcontracting_flow",
|
||||
"connections_tab"
|
||||
],
|
||||
"fields": [
|
||||
@@ -516,23 +516,6 @@
|
||||
"options": "Pricing Rule Detail",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible_depends_on": "supplied_items",
|
||||
"fieldname": "raw_material_details",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Raw Materials Supplied"
|
||||
},
|
||||
{
|
||||
"fieldname": "supplied_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Supplied Items",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "po_raw_material_details",
|
||||
"oldfieldtype": "Table",
|
||||
"options": "Purchase Order Item Supplied",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "sb_last_purchase",
|
||||
"fieldtype": "Section Break"
|
||||
@@ -1142,14 +1125,6 @@
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_old_subcontracting_flow",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Is Old Subcontracting Flow",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "is_internal_supplier",
|
||||
"fieldname": "set_from_warehouse",
|
||||
@@ -1330,7 +1305,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-30 12:17:43.342204",
|
||||
"modified": "2026-05-04 10:10:22.608381",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
||||
@@ -49,9 +49,6 @@ class PurchaseOrder(BuyingController):
|
||||
PurchaseTaxesandCharges,
|
||||
)
|
||||
from erpnext.buying.doctype.purchase_order_item.purchase_order_item import PurchaseOrderItem
|
||||
from erpnext.buying.doctype.purchase_order_item_supplied.purchase_order_item_supplied import (
|
||||
PurchaseOrderItemSupplied,
|
||||
)
|
||||
|
||||
additional_discount_percentage: DF.Float
|
||||
address_display: DF.TextEditor | None
|
||||
@@ -100,7 +97,6 @@ class PurchaseOrder(BuyingController):
|
||||
incoterm: DF.Link | None
|
||||
inter_company_order_reference: DF.Link | None
|
||||
is_internal_supplier: DF.Check
|
||||
is_old_subcontracting_flow: DF.Check
|
||||
is_subcontracted: DF.Check
|
||||
item_wise_tax_details: DF.Table[ItemWiseTaxDetail]
|
||||
items: DF.Table[PurchaseOrderItem]
|
||||
@@ -147,7 +143,6 @@ class PurchaseOrder(BuyingController):
|
||||
"Closed",
|
||||
"Delivered",
|
||||
]
|
||||
supplied_items: DF.Table[PurchaseOrderItemSupplied]
|
||||
supplier: DF.Link
|
||||
supplier_address: DF.Link | None
|
||||
supplier_group: DF.Link | None
|
||||
@@ -210,14 +205,9 @@ class PurchaseOrder(BuyingController):
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_for_subcontracting()
|
||||
self.validate_minimum_order_qty()
|
||||
validate_against_blanket_order(self)
|
||||
|
||||
if self.is_old_subcontracting_flow:
|
||||
self.validate_bom_for_subcontracting_items()
|
||||
self.create_raw_materials_supplied()
|
||||
|
||||
self.validate_fg_item_for_subcontracting()
|
||||
self.set_received_qty_for_drop_ship_items()
|
||||
|
||||
@@ -328,40 +318,30 @@ class PurchaseOrder(BuyingController):
|
||||
).format(item_code, qty, itemwise_min_order_qty.get(item_code))
|
||||
)
|
||||
|
||||
def validate_bom_for_subcontracting_items(self):
|
||||
for item in self.items:
|
||||
if not item.bom:
|
||||
frappe.throw(
|
||||
_("Row #{0}: BOM is not specified for subcontracting item {0}").format(
|
||||
item.idx, item.item_code
|
||||
)
|
||||
)
|
||||
|
||||
def validate_fg_item_for_subcontracting(self):
|
||||
if self.is_subcontracted:
|
||||
if not self.is_old_subcontracting_flow:
|
||||
for item in self.items:
|
||||
if not item.fg_item:
|
||||
for item in self.items:
|
||||
if not item.fg_item:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Finished Good Item is not specified for service item {1}").format(
|
||||
item.idx, item.item_code
|
||||
)
|
||||
)
|
||||
else:
|
||||
if not frappe.get_value("Item", item.fg_item, "is_sub_contracted_item"):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Finished Good Item is not specified for service item {1}").format(
|
||||
item.idx, item.item_code
|
||||
_("Row #{0}: Finished Good Item {1} must be a sub-contracted item").format(
|
||||
item.idx, item.fg_item
|
||||
)
|
||||
)
|
||||
else:
|
||||
if not frappe.get_value("Item", item.fg_item, "is_sub_contracted_item"):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Finished Good Item {1} must be a sub-contracted item").format(
|
||||
item.idx, item.fg_item
|
||||
)
|
||||
elif not item.bom and not frappe.get_value("Item", item.fg_item, "default_bom"):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Default BOM not found for FG Item {1}").format(
|
||||
item.idx, item.fg_item
|
||||
)
|
||||
elif not item.bom and not frappe.get_value("Item", item.fg_item, "default_bom"):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Default BOM not found for FG Item {1}").format(
|
||||
item.idx, item.fg_item
|
||||
)
|
||||
)
|
||||
if not item.fg_item_qty:
|
||||
frappe.throw(_("Row #{0}: Finished Good Item Qty can not be zero").format(item.idx))
|
||||
)
|
||||
if not item.fg_item_qty:
|
||||
frappe.throw(_("Row #{0}: Finished Good Item Qty can not be zero").format(item.idx))
|
||||
else:
|
||||
for item in self.items:
|
||||
item.set("fg_item", None)
|
||||
@@ -442,7 +422,6 @@ class PurchaseOrder(BuyingController):
|
||||
self.set_status(update=True, status=status)
|
||||
self.update_requested_qty()
|
||||
self.update_ordered_qty()
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
self.update_subcontracting_order_status()
|
||||
self.update_blanket_order()
|
||||
self.notify_update()
|
||||
@@ -458,12 +437,11 @@ class PurchaseOrder(BuyingController):
|
||||
self.update_status_updater_if_from_pp()
|
||||
|
||||
self.update_prevdoc_status()
|
||||
if not self.is_subcontracted or self.is_old_subcontracting_flow:
|
||||
if not self.is_subcontracted:
|
||||
self.update_requested_qty()
|
||||
|
||||
self.update_ordered_qty()
|
||||
self.validate_budget()
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
|
||||
frappe.get_cached_doc("Authorization Control").validate_approving_authority(
|
||||
self.doctype, self.company, self.base_grand_total
|
||||
@@ -495,7 +473,6 @@ class PurchaseOrder(BuyingController):
|
||||
if self.has_drop_ship_item():
|
||||
self.update_delivered_qty_in_sales_order()
|
||||
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
self.check_on_hold_or_closed_status()
|
||||
|
||||
self.db_set("status", "Cancelled")
|
||||
@@ -504,7 +481,7 @@ class PurchaseOrder(BuyingController):
|
||||
|
||||
# Must be called after updating ordered qty in Material Request
|
||||
# bin uses Material Request Items to recalculate & update
|
||||
if not self.is_subcontracted or self.is_old_subcontracting_flow:
|
||||
if not self.is_subcontracted:
|
||||
self.update_requested_qty()
|
||||
|
||||
self.update_ordered_qty()
|
||||
@@ -581,13 +558,6 @@ class PurchaseOrder(BuyingController):
|
||||
if item.delivered_by_supplier == 1:
|
||||
item.received_qty = item.qty
|
||||
|
||||
def update_reserved_qty_for_subcontract(self):
|
||||
if self.is_old_subcontracting_flow:
|
||||
for d in self.supplied_items:
|
||||
if d.rm_item_code:
|
||||
stock_bin = get_bin(d.rm_item_code, d.reserve_warehouse)
|
||||
stock_bin.update_reserved_qty_for_sub_contracting(subcontract_doctype="Purchase Order")
|
||||
|
||||
def update_receiving_percentage(self):
|
||||
total_qty, received_qty = 0.0, 0.0
|
||||
for item in self.items:
|
||||
@@ -599,7 +569,7 @@ class PurchaseOrder(BuyingController):
|
||||
self.db_set("per_received", 0, update_modified=False)
|
||||
|
||||
def set_service_items_for_finished_goods(self):
|
||||
if not self.is_subcontracted or self.is_old_subcontracting_flow:
|
||||
if not self.is_subcontracted:
|
||||
return
|
||||
|
||||
finished_goods_without_service_item = {
|
||||
@@ -620,7 +590,7 @@ class PurchaseOrder(BuyingController):
|
||||
def can_update_items(self) -> bool:
|
||||
result = True
|
||||
|
||||
if self.is_subcontracted and not self.is_old_subcontracting_flow:
|
||||
if self.is_subcontracted:
|
||||
if frappe.db.exists(
|
||||
"Subcontracting Order", {"purchase_order": self.name, "docstatus": ["!=", 2]}
|
||||
):
|
||||
@@ -644,7 +614,7 @@ class PurchaseOrder(BuyingController):
|
||||
)
|
||||
|
||||
def auto_create_subcontracting_order(self):
|
||||
if self.is_subcontracted and not self.is_old_subcontracting_flow:
|
||||
if self.is_subcontracted:
|
||||
if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):
|
||||
make_subcontracting_order(self.name, save=True, notify=True)
|
||||
|
||||
@@ -653,7 +623,7 @@ class PurchaseOrder(BuyingController):
|
||||
update_subcontracting_order_status as update_sco_status,
|
||||
)
|
||||
|
||||
if self.is_subcontracted and not self.is_old_subcontracting_flow:
|
||||
if self.is_subcontracted:
|
||||
sco = frappe.db.get_value("Subcontracting Order", {"purchase_order": self.name, "docstatus": 1})
|
||||
|
||||
if sco:
|
||||
|
||||
@@ -1593,11 +1593,6 @@ def create_purchase_order(**args):
|
||||
po.set_missing_values()
|
||||
po.insert()
|
||||
if not args.do_not_submit:
|
||||
if po.is_subcontracted:
|
||||
supp_items = po.get("supplied_items")
|
||||
for d in supp_items:
|
||||
if not d.reserve_warehouse:
|
||||
d.reserve_warehouse = args.warehouse or "_Test Warehouse - _TC"
|
||||
po.submit()
|
||||
|
||||
return po
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_bulk_edit": 1,
|
||||
"autoname": "hash",
|
||||
"creation": "2024-12-09 12:54:24.652161",
|
||||
"doctype": "DocType",
|
||||
@@ -94,7 +95,6 @@
|
||||
"manufacturer_part_no",
|
||||
"column_break_14",
|
||||
"bom",
|
||||
"include_exploded_items",
|
||||
"item_weight_details",
|
||||
"weight_per_unit",
|
||||
"total_weight",
|
||||
@@ -588,22 +588,12 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "bom",
|
||||
"fieldtype": "Link",
|
||||
"label": "BOM",
|
||||
"options": "BOM",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!parent.is_old_subcontracting_flow"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "include_exploded_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Include Exploded Items",
|
||||
"print_hide": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_56",
|
||||
@@ -870,20 +860,20 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||
"depends_on": "eval:parent.is_subcontracted",
|
||||
"fieldname": "fg_item",
|
||||
"fieldtype": "Link",
|
||||
"label": "Finished Good",
|
||||
"mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||
"mandatory_depends_on": "eval:parent.is_subcontracted",
|
||||
"options": "Item"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||
"depends_on": "eval:parent.is_subcontracted",
|
||||
"fieldname": "fg_item_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Finished Good Qty",
|
||||
"mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||
"mandatory_depends_on": "eval:parent.is_subcontracted",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
@@ -935,7 +925,7 @@
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||
"depends_on": "eval:parent.is_subcontracted",
|
||||
"fieldname": "subcontracted_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Subcontracted Quantity",
|
||||
@@ -950,7 +940,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-11-30 16:51:57.761673",
|
||||
"modified": "2026-05-04 10:51:05.183490",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item",
|
||||
|
||||
@@ -27,7 +27,6 @@ class PurchaseOrderItem(Document):
|
||||
billed_amt: DF.Currency
|
||||
blanket_order: DF.Link | None
|
||||
blanket_order_rate: DF.Currency
|
||||
bom: DF.Link | None
|
||||
brand: DF.Link | None
|
||||
company_total_stock: DF.Float
|
||||
conversion_factor: DF.Float
|
||||
@@ -43,7 +42,6 @@ class PurchaseOrderItem(Document):
|
||||
fg_item_qty: DF.Float
|
||||
from_warehouse: DF.Link | None
|
||||
image: DF.Attach | None
|
||||
include_exploded_items: DF.Check
|
||||
is_fixed_asset: DF.Check
|
||||
is_free_item: DF.Check
|
||||
item_code: DF.Link
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Raw material (if any) supplied along with Purchase Order.
|
||||
@@ -1,195 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2013-02-22 01:27:42",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"main_item_code",
|
||||
"rm_item_code",
|
||||
"column_break_3",
|
||||
"stock_uom",
|
||||
"reserve_warehouse",
|
||||
"conversion_factor",
|
||||
"column_break_6",
|
||||
"bom_detail_no",
|
||||
"reference_name",
|
||||
"section_break2",
|
||||
"rate",
|
||||
"col_break2",
|
||||
"amount",
|
||||
"section_break1",
|
||||
"required_qty",
|
||||
"supplied_qty",
|
||||
"col_break1",
|
||||
"consumed_qty",
|
||||
"returned_qty",
|
||||
"total_supplied_qty"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "main_item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"oldfieldname": "main_item_code",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "rm_item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Raw Material Item Code",
|
||||
"oldfieldname": "rm_item_code",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "required_qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Required Qty",
|
||||
"oldfieldname": "required_qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Rate",
|
||||
"oldfieldname": "rate",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount",
|
||||
"oldfieldname": "amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "bom_detail_no",
|
||||
"fieldtype": "Data",
|
||||
"label": "BOM Detail No",
|
||||
"oldfieldname": "bom_detail_no",
|
||||
"oldfieldtype": "Data",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reference Name",
|
||||
"oldfieldname": "reference_name",
|
||||
"oldfieldtype": "Data",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "conversion_factor",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"label": "Conversion Factor",
|
||||
"oldfieldname": "conversion_factor",
|
||||
"oldfieldtype": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "Stock Uom",
|
||||
"oldfieldname": "stock_uom",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "UOM",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "reserve_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Reserve Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"fieldname": "supplied_qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Supplied Qty",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break1",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break1",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break2",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "consumed_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Consumed Qty",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "returned_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Returned Qty",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "total_supplied_qty",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"label": "Total Supplied Qty",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:25.280016",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item Supplied",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class PurchaseOrderItemSupplied(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
|
||||
|
||||
amount: DF.Currency
|
||||
bom_detail_no: DF.Data | None
|
||||
consumed_qty: DF.Float
|
||||
conversion_factor: DF.Float
|
||||
main_item_code: DF.Link | None
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
rate: DF.Currency
|
||||
reference_name: DF.Data | None
|
||||
required_qty: DF.Float
|
||||
reserve_warehouse: DF.Link | None
|
||||
returned_qty: DF.Float
|
||||
rm_item_code: DF.Link | None
|
||||
stock_uom: DF.Link | None
|
||||
supplied_qty: DF.Float
|
||||
total_supplied_qty: DF.Float
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -25,13 +25,6 @@ frappe.query_reports["Subcontract Order Summary"] = {
|
||||
default: frappe.datetime.get_today(),
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
label: __("Order Type"),
|
||||
fieldname: "order_type",
|
||||
fieldtype: "Select",
|
||||
options: ["Purchase Order", "Subcontracting Order"],
|
||||
default: "Subcontracting Order",
|
||||
},
|
||||
{
|
||||
label: __("Subcontract Order"),
|
||||
fieldname: "name",
|
||||
|
||||
@@ -28,37 +28,34 @@ def get_data(report_filters):
|
||||
|
||||
def get_subcontracted_orders(report_filters):
|
||||
fields = [
|
||||
f"`tab{report_filters.order_type} Item`.`parent` as order_id",
|
||||
f"`tab{report_filters.order_type} Item`.`item_code`",
|
||||
f"`tab{report_filters.order_type} Item`.`item_name`",
|
||||
f"`tab{report_filters.order_type} Item`.`qty`",
|
||||
f"`tab{report_filters.order_type} Item`.`name`",
|
||||
f"`tab{report_filters.order_type} Item`.`received_qty`",
|
||||
f"`tab{report_filters.order_type}`.`status`",
|
||||
"`tabSubcontracting Order Item`.`parent` as order_id",
|
||||
"`tabSubcontracting Order Item`.`item_code`",
|
||||
"`tabSubcontracting Order Item`.`item_name`",
|
||||
"`tabSubcontracting Order Item`.`qty`",
|
||||
"`tabSubcontracting Order Item`.`name`",
|
||||
"`tabSubcontracting Order Item`.`received_qty`",
|
||||
"`tabSubcontracting Order`.`status`",
|
||||
]
|
||||
|
||||
filters = get_filters(report_filters)
|
||||
|
||||
return frappe.get_all(report_filters.order_type, fields=fields, filters=filters) or []
|
||||
return frappe.get_all("Subcontracting Order", fields=fields, filters=filters) or []
|
||||
|
||||
|
||||
def get_filters(report_filters):
|
||||
filters = [
|
||||
[report_filters.order_type, "docstatus", "=", 1],
|
||||
["Subcontracting Order", "docstatus", "=", 1],
|
||||
[
|
||||
report_filters.order_type,
|
||||
"Subcontracting Order",
|
||||
"transaction_date",
|
||||
"between",
|
||||
(report_filters.from_date, report_filters.to_date),
|
||||
],
|
||||
]
|
||||
|
||||
if report_filters.order_type == "Purchase Order":
|
||||
filters.append(["Purchase Order", "is_old_subcontracting_flow", "=", 1])
|
||||
|
||||
for field in ["name", "company"]:
|
||||
if report_filters.get(field):
|
||||
filters.append([report_filters.order_type, field, "=", report_filters.get(field)])
|
||||
filters.append(["Subcontracting Order", field, "=", report_filters.get(field)])
|
||||
|
||||
return filters
|
||||
|
||||
@@ -82,12 +79,7 @@ def get_supplied_items(orders, report_filters):
|
||||
filters = {"parent": ("in", [d.order_id for d in orders]), "docstatus": 1}
|
||||
|
||||
supplied_items = {}
|
||||
supplied_items_table = (
|
||||
"Purchase Order Item Supplied"
|
||||
if report_filters.order_type == "Purchase Order"
|
||||
else "Subcontracting Order Supplied Item"
|
||||
)
|
||||
for row in frappe.get_all(supplied_items_table, fields=fields, filters=filters):
|
||||
for row in frappe.get_all("Subcontracting Order Supplied Item", fields=fields, filters=filters):
|
||||
new_key = (row.parent, row.reference_name, row.main_item_code)
|
||||
|
||||
supplied_items.setdefault(new_key, []).append(row)
|
||||
@@ -128,7 +120,7 @@ def get_columns(filters):
|
||||
"label": _("Subcontract Order"),
|
||||
"fieldname": "order_id",
|
||||
"fieldtype": "Link",
|
||||
"options": filters.order_type,
|
||||
"options": "Subcontracting Order",
|
||||
"width": 100,
|
||||
},
|
||||
{"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 80},
|
||||
|
||||
@@ -3,13 +3,6 @@
|
||||
|
||||
frappe.query_reports["Subcontracted Item To Be Received"] = {
|
||||
filters: [
|
||||
{
|
||||
label: __("Order Type"),
|
||||
fieldname: "order_type",
|
||||
fieldtype: "Select",
|
||||
options: ["Purchase Order", "Subcontracting Order"],
|
||||
default: "Subcontracting Order",
|
||||
},
|
||||
{
|
||||
fieldname: "supplier",
|
||||
label: __("Supplier"),
|
||||
|
||||
@@ -22,7 +22,6 @@ def get_columns(filters):
|
||||
"label": _("Subcontract Order"),
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "subcontract_order",
|
||||
"options": filters.order_type,
|
||||
"width": 150,
|
||||
},
|
||||
{"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "hidden": 1, "width": 150},
|
||||
@@ -59,7 +58,7 @@ def get_columns(filters):
|
||||
def get_data(data, filters):
|
||||
orders = get_subcontract_orders(filters)
|
||||
orders_name = [order.name for order in orders]
|
||||
subcontracted_items = get_subcontract_order_supplied_item(filters.order_type, orders_name)
|
||||
subcontracted_items = get_subcontract_order_supplied_item("Subcontracting Order", orders_name)
|
||||
for item in subcontracted_items:
|
||||
for order in orders:
|
||||
if order.name == item.parent and item.received_qty < item.qty:
|
||||
@@ -84,11 +83,8 @@ def get_subcontract_orders(filters):
|
||||
["docstatus", "=", 1],
|
||||
]
|
||||
|
||||
if filters.order_type == "Purchase Order":
|
||||
record_filters.append(["is_old_subcontracting_flow", "=", 1])
|
||||
|
||||
return frappe.get_all(
|
||||
filters.order_type, filters=record_filters, fields=["name", "transaction_date", "supplier"]
|
||||
"Subcontracting Order", filters=record_filters, fields=["name", "transaction_date", "supplier"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -3,13 +3,6 @@
|
||||
|
||||
frappe.query_reports["Subcontracted Raw Materials To Be Transferred"] = {
|
||||
filters: [
|
||||
{
|
||||
label: __("Order Type"),
|
||||
fieldname: "order_type",
|
||||
fieldtype: "Select",
|
||||
options: ["Purchase Order", "Subcontracting Order"],
|
||||
default: "Subcontracting Order",
|
||||
},
|
||||
{
|
||||
fieldname: "supplier",
|
||||
label: __("Supplier"),
|
||||
|
||||
@@ -22,7 +22,6 @@ def get_columns(filters):
|
||||
"label": _("Subcontract Order"),
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "subcontract_order",
|
||||
"options": filters.order_type,
|
||||
"width": 200,
|
||||
},
|
||||
{"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "width": 150},
|
||||
@@ -61,25 +60,18 @@ def get_data(filters):
|
||||
|
||||
|
||||
def get_order_items_to_supply(filters):
|
||||
supplied_items_table = (
|
||||
"Purchase Order Item Supplied"
|
||||
if filters.order_type == "Purchase Order"
|
||||
else "Subcontracting Order Supplied Item"
|
||||
)
|
||||
supplied_items_table = "Subcontracting Order Supplied Item"
|
||||
|
||||
record_filters = [
|
||||
[filters.order_type, "per_received", "<", "100"],
|
||||
[filters.order_type, "supplier", "=", filters.supplier],
|
||||
[filters.order_type, "transaction_date", "<=", filters.to_date],
|
||||
[filters.order_type, "transaction_date", ">=", filters.from_date],
|
||||
[filters.order_type, "docstatus", "=", 1],
|
||||
["Subcontracting Order", "per_received", "<", "100"],
|
||||
["Subcontracting Order", "supplier", "=", filters.supplier],
|
||||
["Subcontracting Order", "transaction_date", "<=", filters.to_date],
|
||||
["Subcontracting Order", "transaction_date", ">=", filters.from_date],
|
||||
["Subcontracting Order", "docstatus", "=", 1],
|
||||
]
|
||||
|
||||
if filters.order_type == "Purchase Order":
|
||||
record_filters.append([filters.order_type, "is_old_subcontracting_flow", "=", 1])
|
||||
|
||||
return frappe.db.get_all(
|
||||
filters.order_type,
|
||||
"Subcontracting Order",
|
||||
fields=[
|
||||
"name as subcontract_order",
|
||||
"transaction_date as date",
|
||||
|
||||
@@ -3856,6 +3856,137 @@ def validate_and_delete_children(parent, data, ordered_item=None) -> bool:
|
||||
return bool(deleted_children)
|
||||
|
||||
|
||||
def get_allow_zero_qty(parent_doctype: str) -> bool:
|
||||
if parent_doctype == "Sales Order":
|
||||
return frappe.db.get_single_value("Selling Settings", "allow_zero_qty_in_sales_order") or False
|
||||
if parent_doctype == "Purchase Order":
|
||||
return frappe.db.get_single_value("Buying Settings", "allow_zero_qty_in_purchase_order") or False
|
||||
return False
|
||||
|
||||
|
||||
def get_child_item_change_state(parent_doctype: str, child_item, new_data) -> frappe._dict:
|
||||
prev_rate, new_rate = flt(child_item.get("rate")), flt(new_data.get("rate"))
|
||||
prev_qty, new_qty = flt(child_item.get("qty")), flt(new_data.get("qty"))
|
||||
prev_fg_qty, new_fg_qty = flt(child_item.get("fg_item_qty")), flt(new_data.get("fg_item_qty"))
|
||||
prev_con_fac, new_con_fac = (
|
||||
flt(child_item.get("conversion_factor")),
|
||||
flt(new_data.get("conversion_factor")),
|
||||
)
|
||||
|
||||
if parent_doctype == "Sales Order":
|
||||
prev_date, new_date = child_item.get("delivery_date"), new_data.get("delivery_date")
|
||||
elif parent_doctype == "Purchase Order":
|
||||
prev_date, new_date = child_item.get("schedule_date"), new_data.get("schedule_date")
|
||||
else:
|
||||
prev_date, new_date = None, None
|
||||
|
||||
if parent_doctype in ["Quotation", "Supplier Quotation"]:
|
||||
date_unchanged = False
|
||||
else:
|
||||
prev_date = getdate(prev_date) if prev_date else None
|
||||
new_date = getdate(new_date) if new_date else None
|
||||
date_unchanged = prev_date == new_date
|
||||
|
||||
return frappe._dict(
|
||||
rate_unchanged=prev_rate == new_rate,
|
||||
qty_unchanged=prev_qty == new_qty,
|
||||
fg_qty_unchanged=prev_fg_qty == new_fg_qty,
|
||||
uom_unchanged=child_item.get("uom") == new_data.get("uom"),
|
||||
conversion_factor_unchanged=prev_con_fac == new_con_fac,
|
||||
date_unchanged=date_unchanged,
|
||||
description_unchanged=child_item.get("description") == new_data.get("description"),
|
||||
)
|
||||
|
||||
|
||||
def is_child_item_unchanged(change_state: frappe._dict) -> bool:
|
||||
return (
|
||||
change_state.rate_unchanged
|
||||
and change_state.qty_unchanged
|
||||
and change_state.fg_qty_unchanged
|
||||
and change_state.conversion_factor_unchanged
|
||||
and change_state.uom_unchanged
|
||||
and change_state.date_unchanged
|
||||
and change_state.description_unchanged
|
||||
)
|
||||
|
||||
|
||||
def update_child_item_rate_and_discount(
|
||||
parent_doctype: str, child_item, new_data, allow_zero_qty: bool, rate_unchanged: bool | None = None
|
||||
) -> None:
|
||||
rate_precision = child_item.precision("rate") or 2
|
||||
qty_precision = child_item.precision("qty") or 2
|
||||
|
||||
if rate_unchanged is None:
|
||||
prev_rate, new_rate = flt(child_item.get("rate")), flt(new_data.get("rate"))
|
||||
rate_unchanged = prev_rate == new_rate
|
||||
|
||||
if not rate_unchanged and not child_item.get("qty") and allow_zero_qty:
|
||||
frappe.throw(_("Rate of '{}' items cannot be changed").format(frappe.bold(_("Unit Price"))))
|
||||
|
||||
# Amount cannot be lesser than billed amount, except for negative amounts
|
||||
row_rate = flt(new_data.get("rate"), rate_precision)
|
||||
|
||||
if parent_doctype in ["Purchase Order", "Sales Order"]:
|
||||
amount_below_billed_amt = flt(child_item.billed_amt, rate_precision) > flt(
|
||||
row_rate * flt(new_data.get("qty"), qty_precision), rate_precision
|
||||
)
|
||||
if amount_below_billed_amt and row_rate > 0.0:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: Cannot set Rate if the billed amount is greater than the amount for Item {1}."
|
||||
).format(child_item.idx, child_item.item_code)
|
||||
)
|
||||
|
||||
child_item.rate = row_rate
|
||||
|
||||
if parent_doctype not in ["Sales Order", "Purchase Order"] or not flt(child_item.price_list_rate):
|
||||
return
|
||||
|
||||
if flt(child_item.rate) > flt(child_item.price_list_rate):
|
||||
# if rate is greater than price_list_rate, set margin or set discount
|
||||
child_item.discount_percentage = 0
|
||||
child_item.margin_type = "Amount"
|
||||
child_item.margin_rate_or_amount = flt(
|
||||
child_item.rate - child_item.price_list_rate,
|
||||
child_item.precision("margin_rate_or_amount"),
|
||||
)
|
||||
child_item.rate_with_margin = child_item.rate
|
||||
else:
|
||||
child_item.discount_percentage = flt(
|
||||
(1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0,
|
||||
child_item.precision("discount_percentage"),
|
||||
)
|
||||
child_item.discount_amount = flt(child_item.price_list_rate) - flt(child_item.rate)
|
||||
child_item.margin_type = ""
|
||||
child_item.margin_rate_or_amount = 0
|
||||
child_item.rate_with_margin = 0
|
||||
|
||||
|
||||
def update_child_item_uom_and_weight(child_item, new_data) -> None:
|
||||
conv_fac_precision = child_item.precision("conversion_factor") or 2
|
||||
|
||||
if new_data.get("conversion_factor"):
|
||||
if child_item.stock_uom == child_item.uom:
|
||||
child_item.conversion_factor = 1
|
||||
else:
|
||||
child_item.conversion_factor = flt(new_data.get("conversion_factor"), conv_fac_precision)
|
||||
|
||||
if new_data.get("uom"):
|
||||
child_item.uom = new_data.get("uom")
|
||||
conversion_factor = flt(
|
||||
get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor")
|
||||
)
|
||||
child_item.conversion_factor = (
|
||||
flt(new_data.get("conversion_factor"), conv_fac_precision) or conversion_factor
|
||||
)
|
||||
|
||||
if child_item.get("total_weight") and child_item.get("weight_per_unit"):
|
||||
child_item.total_weight = flt(
|
||||
child_item.weight_per_unit * child_item.qty * child_item.conversion_factor,
|
||||
child_item.precision("total_weight"),
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_child_qty_rate(
|
||||
parent_doctype: str, trans_items: str, parent_doctype_name: str, child_docname: str = "items"
|
||||
@@ -3904,15 +4035,8 @@ def update_child_qty_rate(
|
||||
child_doctype = parent_doctype + " Item"
|
||||
return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row)
|
||||
|
||||
def is_allowed_zero_qty():
|
||||
if parent_doctype == "Sales Order":
|
||||
return frappe.db.get_single_value("Selling Settings", "allow_zero_qty_in_sales_order") or False
|
||||
elif parent_doctype == "Purchase Order":
|
||||
return frappe.db.get_single_value("Buying Settings", "allow_zero_qty_in_purchase_order") or False
|
||||
return False
|
||||
|
||||
def validate_quantity_and_rate(child_item, new_data):
|
||||
if not flt(new_data.get("qty")) and not is_allowed_zero_qty():
|
||||
if not flt(new_data.get("qty")) and not allow_zero_qty:
|
||||
frappe.throw(
|
||||
_("Row #{0}:Quantity for Item {1} cannot be zero.").format(
|
||||
new_data.get("idx"), frappe.bold(new_data.get("item_code"))
|
||||
@@ -4004,6 +4128,7 @@ def update_child_qty_rate(
|
||||
any_conversion_factor_changed = False
|
||||
|
||||
parent = frappe.get_doc(parent_doctype, parent_doctype_name)
|
||||
allow_zero_qty = get_allow_zero_qty(parent_doctype)
|
||||
|
||||
check_doc_permissions(parent, "write")
|
||||
|
||||
@@ -4020,6 +4145,7 @@ def update_child_qty_rate(
|
||||
|
||||
for d in data:
|
||||
new_child_flag = False
|
||||
rate_unchanged = None
|
||||
|
||||
if not d.get("item_code"):
|
||||
# ignore empty rows
|
||||
@@ -4034,42 +4160,10 @@ def update_child_qty_rate(
|
||||
check_doc_permissions(parent, "write")
|
||||
child_item = frappe.get_doc(parent_doctype + " Item", d.get("docname"))
|
||||
|
||||
prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
|
||||
prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty"))
|
||||
prev_fg_qty, new_fg_qty = flt(child_item.get("fg_item_qty")), flt(d.get("fg_item_qty"))
|
||||
prev_con_fac, new_con_fac = (
|
||||
flt(child_item.get("conversion_factor")),
|
||||
flt(d.get("conversion_factor")),
|
||||
)
|
||||
prev_uom, new_uom = child_item.get("uom"), d.get("uom")
|
||||
|
||||
if parent_doctype == "Sales Order":
|
||||
prev_date, new_date = child_item.get("delivery_date"), d.get("delivery_date")
|
||||
elif parent_doctype == "Purchase Order":
|
||||
prev_date, new_date = child_item.get("schedule_date"), d.get("schedule_date")
|
||||
|
||||
prev_description, new_description = (child_item.get("description"), d.get("description"))
|
||||
description_unchanged = prev_description == new_description
|
||||
rate_unchanged = prev_rate == new_rate
|
||||
qty_unchanged = prev_qty == new_qty
|
||||
fg_qty_unchanged = prev_fg_qty == new_fg_qty
|
||||
uom_unchanged = prev_uom == new_uom
|
||||
conversion_factor_unchanged = prev_con_fac == new_con_fac
|
||||
any_conversion_factor_changed |= not conversion_factor_unchanged
|
||||
date_unchanged = (
|
||||
(prev_date == getdate(new_date) if prev_date and new_date else False)
|
||||
if parent_doctype not in ["Quotation", "Supplier Quotation"]
|
||||
else None
|
||||
) # in case of delivery note etc
|
||||
if (
|
||||
rate_unchanged
|
||||
and qty_unchanged
|
||||
and fg_qty_unchanged
|
||||
and conversion_factor_unchanged
|
||||
and uom_unchanged
|
||||
and date_unchanged
|
||||
and description_unchanged
|
||||
):
|
||||
change_state = get_child_item_change_state(parent_doctype, child_item, d)
|
||||
rate_unchanged = change_state.rate_unchanged
|
||||
any_conversion_factor_changed |= not change_state.conversion_factor_unchanged
|
||||
if is_child_item_unchanged(change_state):
|
||||
continue
|
||||
|
||||
validate_quantity_and_rate(child_item, d)
|
||||
@@ -4077,11 +4171,7 @@ def update_child_qty_rate(
|
||||
if flt(child_item.get("qty")) != flt(d.get("qty")):
|
||||
any_qty_changed = True
|
||||
|
||||
if (
|
||||
parent.doctype in ["Sales Order", "Purchase Order"]
|
||||
and parent.is_subcontracted
|
||||
and not parent.get("is_old_subcontracting_flow")
|
||||
):
|
||||
if parent.doctype in ["Sales Order", "Purchase Order"] and parent.is_subcontracted:
|
||||
validate_fg_item_for_subcontracting(d, new_child_flag)
|
||||
child_item.fg_item_qty = flt(d["fg_item_qty"])
|
||||
|
||||
@@ -4090,52 +4180,10 @@ def update_child_qty_rate(
|
||||
|
||||
child_item.qty = flt(d.get("qty"))
|
||||
child_item.description = d.get("description")
|
||||
rate_precision = child_item.precision("rate") or 2
|
||||
conv_fac_precision = child_item.precision("conversion_factor") or 2
|
||||
qty_precision = child_item.precision("qty") or 2
|
||||
|
||||
prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
|
||||
rate_unchanged = prev_rate == new_rate
|
||||
if not rate_unchanged and not child_item.get("qty") and is_allowed_zero_qty():
|
||||
frappe.throw(_("Rate of '{}' items cannot be changed").format(frappe.bold(_("Unit Price"))))
|
||||
# Amount cannot be lesser than billed amount, except for negative amounts
|
||||
row_rate = flt(d.get("rate"), rate_precision)
|
||||
|
||||
if parent_doctype in ["Purchase Order", "Sales Order"]:
|
||||
amount_below_billed_amt = flt(child_item.billed_amt, rate_precision) > flt(
|
||||
row_rate * flt(d.get("qty"), qty_precision), rate_precision
|
||||
)
|
||||
if amount_below_billed_amt and row_rate > 0.0:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: Cannot set Rate if the billed amount is greater than the amount for Item {1}."
|
||||
).format(child_item.idx, child_item.item_code)
|
||||
)
|
||||
else:
|
||||
child_item.rate = row_rate
|
||||
else:
|
||||
child_item.rate = row_rate
|
||||
|
||||
if d.get("conversion_factor"):
|
||||
if child_item.stock_uom == child_item.uom:
|
||||
child_item.conversion_factor = 1
|
||||
else:
|
||||
child_item.conversion_factor = flt(d.get("conversion_factor"), conv_fac_precision)
|
||||
|
||||
if d.get("uom"):
|
||||
child_item.uom = d.get("uom")
|
||||
conversion_factor = flt(
|
||||
get_conversion_factor(child_item.item_code, child_item.uom).get("conversion_factor")
|
||||
)
|
||||
child_item.conversion_factor = (
|
||||
flt(d.get("conversion_factor"), conv_fac_precision) or conversion_factor
|
||||
)
|
||||
|
||||
if child_item.get("total_weight") and child_item.get("weight_per_unit"):
|
||||
child_item.total_weight = flt(
|
||||
child_item.weight_per_unit * child_item.qty * child_item.conversion_factor,
|
||||
child_item.precision("total_weight"),
|
||||
)
|
||||
update_child_item_rate_and_discount(
|
||||
parent_doctype, child_item, d, allow_zero_qty, rate_unchanged=rate_unchanged
|
||||
)
|
||||
update_child_item_uom_and_weight(child_item, d)
|
||||
|
||||
if d.get("delivery_date") and parent_doctype == "Sales Order":
|
||||
child_item.delivery_date = d.get("delivery_date")
|
||||
@@ -4146,28 +4194,6 @@ def update_child_qty_rate(
|
||||
if d.get("bom_no") and parent_doctype == "Sales Order":
|
||||
child_item.bom_no = d.get("bom_no")
|
||||
|
||||
if parent_doctype in ["Sales Order", "Purchase Order"]:
|
||||
if flt(child_item.price_list_rate):
|
||||
if flt(child_item.rate) > flt(child_item.price_list_rate):
|
||||
# if rate is greater than price_list_rate, set margin
|
||||
# or set discount
|
||||
child_item.discount_percentage = 0
|
||||
child_item.margin_type = "Amount"
|
||||
child_item.margin_rate_or_amount = flt(
|
||||
child_item.rate - child_item.price_list_rate,
|
||||
child_item.precision("margin_rate_or_amount"),
|
||||
)
|
||||
child_item.rate_with_margin = child_item.rate
|
||||
else:
|
||||
child_item.discount_percentage = flt(
|
||||
(1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0,
|
||||
child_item.precision("discount_percentage"),
|
||||
)
|
||||
child_item.discount_amount = flt(child_item.price_list_rate) - flt(child_item.rate)
|
||||
child_item.margin_type = ""
|
||||
child_item.margin_rate_or_amount = 0
|
||||
child_item.rate_with_margin = 0
|
||||
|
||||
child_item.flags.ignore_validate_update_after_submit = True
|
||||
if new_child_flag:
|
||||
parent.load_from_db()
|
||||
@@ -4216,18 +4242,12 @@ def update_child_qty_rate(
|
||||
parent.update_receiving_percentage()
|
||||
|
||||
if parent.is_subcontracted:
|
||||
if parent.is_old_subcontracting_flow:
|
||||
if should_update_supplied_items(parent):
|
||||
parent.update_reserved_qty_for_subcontract()
|
||||
parent.create_raw_materials_supplied()
|
||||
parent.save()
|
||||
else:
|
||||
if not parent.can_update_items():
|
||||
frappe.throw(
|
||||
_(
|
||||
"Items cannot be updated as Subcontracting Order is created against the Purchase Order {0}."
|
||||
).format(frappe.bold(parent.name))
|
||||
)
|
||||
if not parent.can_update_items():
|
||||
frappe.throw(
|
||||
_(
|
||||
"Items cannot be updated as Subcontracting Order is created against the Purchase Order {0}."
|
||||
).format(frappe.bold(parent.name))
|
||||
)
|
||||
elif parent_doctype == "Sales Order": # Sales Order
|
||||
if parent.is_subcontracted and not parent.can_update_items():
|
||||
frappe.throw(
|
||||
|
||||
@@ -59,11 +59,6 @@ class BuyingController(SubcontractingController):
|
||||
self.validate_rejected_warehouse()
|
||||
self.validate_accepted_rejected_qty()
|
||||
validate_for_items(self)
|
||||
|
||||
# sub-contracting
|
||||
self.validate_for_subcontracting()
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
self.create_raw_materials_supplied()
|
||||
self.set_landed_cost_voucher_amount()
|
||||
|
||||
if self.doctype in ("Purchase Receipt", "Purchase Invoice"):
|
||||
@@ -488,21 +483,12 @@ class BuyingController(SubcontractingController):
|
||||
if not qty_in_stock_uom and item.get("rejected_qty"):
|
||||
qty_in_stock_uom = flt(item.rejected_qty * item.conversion_factor)
|
||||
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
|
||||
item.valuation_rate = (
|
||||
net_rate
|
||||
+ item.item_tax_amount
|
||||
+ item.rm_supp_cost
|
||||
+ flt(item.landed_cost_voucher_amount)
|
||||
) / qty_in_stock_uom
|
||||
else:
|
||||
item.valuation_rate = (
|
||||
net_rate
|
||||
+ item.item_tax_amount
|
||||
+ flt(item.landed_cost_voucher_amount)
|
||||
+ flt(item.get("amount_difference_with_purchase_invoice"))
|
||||
) / qty_in_stock_uom
|
||||
item.valuation_rate = (
|
||||
net_rate
|
||||
+ item.item_tax_amount
|
||||
+ flt(item.landed_cost_voucher_amount)
|
||||
+ flt(item.get("amount_difference_with_purchase_invoice"))
|
||||
) / qty_in_stock_uom
|
||||
else:
|
||||
item.valuation_rate = 0.0
|
||||
|
||||
@@ -642,36 +628,6 @@ class BuyingController(SubcontractingController):
|
||||
* (d.conversion_factor or 1)
|
||||
)
|
||||
|
||||
def validate_for_subcontracting(self):
|
||||
if self.is_subcontracted and self.get("is_old_subcontracting_flow"):
|
||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse:
|
||||
frappe.throw(
|
||||
_("{field_label} is mandatory for sub-contracted {doctype}.").format(
|
||||
field_label=_(self.meta.get_label("supplier_warehouse")), doctype=_(self.doctype)
|
||||
)
|
||||
)
|
||||
|
||||
for item in self.get("items"):
|
||||
if item in self.sub_contracted_items and not item.bom:
|
||||
frappe.throw(
|
||||
_("Please select BOM in BOM field for Item {item_code}.").format(
|
||||
item_code=frappe.bold(item.item_code)
|
||||
)
|
||||
)
|
||||
if self.doctype != "Purchase Order":
|
||||
return
|
||||
for row in self.get("supplied_items"):
|
||||
if not row.reserve_warehouse:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Reserved Warehouse is mandatory for the Item {item_code} in Raw Materials supplied."
|
||||
).format(item_code=frappe.bold(row.rm_item_code))
|
||||
)
|
||||
else:
|
||||
for item in self.get("items"):
|
||||
if item.get("bom"):
|
||||
item.bom = None
|
||||
|
||||
def set_qty_as_per_stock_uom(self):
|
||||
allow_to_edit_stock_qty = frappe.get_single_value(
|
||||
"Stock Settings", "allow_to_edit_stock_uom_qty_for_purchase"
|
||||
@@ -885,9 +841,6 @@ class BuyingController(SubcontractingController):
|
||||
)
|
||||
)
|
||||
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
self.make_sl_entries_for_supplier_warehouse(sl_entries)
|
||||
|
||||
self.make_sl_entries(
|
||||
sl_entries,
|
||||
allow_negative_stock=allow_negative_stock,
|
||||
@@ -943,8 +896,6 @@ class BuyingController(SubcontractingController):
|
||||
)
|
||||
|
||||
po_obj.update_ordered_qty(po_item_rows)
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
po_obj.update_reserved_qty_for_subcontract()
|
||||
|
||||
def on_submit(self):
|
||||
if self.get("is_return"):
|
||||
@@ -1213,10 +1164,7 @@ class BuyingController(SubcontractingController):
|
||||
if self.doctype == "Material Request":
|
||||
return
|
||||
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
validate_item_type(self, "is_sub_contracted_item", "subcontracted")
|
||||
else:
|
||||
validate_item_type(self, "is_purchase_item", "purchase")
|
||||
validate_item_type(self, "is_purchase_item", "purchase")
|
||||
|
||||
|
||||
def get_asset_item_details(asset_items):
|
||||
|
||||
@@ -26,17 +26,7 @@ from erpnext.stock.utils import get_incoming_rate
|
||||
class SubcontractingController(StockController):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
self.subcontract_data = frappe._dict(
|
||||
{
|
||||
"order_doctype": "Purchase Order",
|
||||
"order_field": "purchase_order",
|
||||
"rm_detail_field": "po_detail",
|
||||
"receipt_supplied_items_field": "Purchase Receipt Item Supplied",
|
||||
"order_supplied_items_field": "Purchase Order Item Supplied",
|
||||
}
|
||||
)
|
||||
elif self.doctype == "Subcontracting Inward Order":
|
||||
if self.doctype == "Subcontracting Inward Order":
|
||||
self.subcontract_data = frappe._dict(
|
||||
{
|
||||
"order_doctype": "Subcontracting Inward Order",
|
||||
@@ -541,11 +531,7 @@ class SubcontractingController(StockController):
|
||||
self.__set_alternative_item_details(row)
|
||||
|
||||
self.__transferred_items = copy.deepcopy(self.available_materials)
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
for doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
self.__update_consumed_materials(doctype)
|
||||
else:
|
||||
self.__update_consumed_materials("Subcontracting Receipt")
|
||||
self.__update_consumed_materials("Subcontracting Receipt")
|
||||
|
||||
def __remove_changed_rows(self):
|
||||
if not self.__changed_name:
|
||||
@@ -1136,24 +1122,17 @@ class SubcontractingController(StockController):
|
||||
|
||||
def set_consumed_qty_in_subcontract_order(self):
|
||||
# Update consumed qty back in the subcontract order
|
||||
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"] or self.get(
|
||||
"is_old_subcontracting_flow"
|
||||
):
|
||||
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
|
||||
self.__get_subcontract_orders()
|
||||
itemwise_consumed_qty = defaultdict(float)
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
doctypes = ["Purchase Receipt", "Purchase Invoice"]
|
||||
else:
|
||||
doctypes = ["Subcontracting Receipt"]
|
||||
|
||||
for doctype in doctypes:
|
||||
consumed_items, receipt_items = self.__update_consumed_materials(
|
||||
doctype, return_consumed_items=True
|
||||
)
|
||||
consumed_items, receipt_items = self.__update_consumed_materials(
|
||||
"Subcontracting Receipt", return_consumed_items=True
|
||||
)
|
||||
|
||||
for row in consumed_items:
|
||||
key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name))
|
||||
itemwise_consumed_qty[key] += row.consumed_qty
|
||||
for row in consumed_items:
|
||||
key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name))
|
||||
itemwise_consumed_qty[key] += row.consumed_qty
|
||||
|
||||
self.__update_consumed_qty_in_subcontract_order(itemwise_consumed_qty)
|
||||
|
||||
@@ -1236,32 +1215,10 @@ class SubcontractingController(StockController):
|
||||
via_landed_cost_voucher=via_landed_cost_voucher,
|
||||
)
|
||||
|
||||
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
|
||||
def get_supplied_items_cost(self, item_row_id):
|
||||
supplied_items_cost = 0.0
|
||||
for item in self.get("supplied_items"):
|
||||
if item.reference_name == item_row_id:
|
||||
if (
|
||||
self.get("is_old_subcontracting_flow")
|
||||
and reset_outgoing_rate
|
||||
and frappe.get_cached_value("Item", item.rm_item_code, "is_stock_item")
|
||||
):
|
||||
rate = get_incoming_rate(
|
||||
{
|
||||
"item_code": item.rm_item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"qty": -1 * item.consumed_qty,
|
||||
"voucher_detail_no": item.name,
|
||||
"serial_and_batch_bundle": item.get("serial_and_batch_bundle"),
|
||||
"serial_no": item.get("serial_no"),
|
||||
"batch_no": item.get("batch_no"),
|
||||
}
|
||||
)
|
||||
|
||||
if rate > 0:
|
||||
item.rate = rate
|
||||
|
||||
item.amount = flt(flt(item.consumed_qty) * flt(item.rate), item.precision("amount"))
|
||||
supplied_items_cost += item.amount
|
||||
|
||||
|
||||
@@ -470,7 +470,7 @@ erpnext.patches.v16_0.add_portal_redirects
|
||||
erpnext.patches.v16_0.update_order_qty_and_requested_qty_based_on_mr_and_po
|
||||
erpnext.patches.v16_0.complete_onboarding_steps_for_older_sites #2
|
||||
erpnext.patches.v16_0.enable_serial_batch_setting
|
||||
erpnext.patches.v16_0.correct_po_titles
|
||||
erpnext.patches.v16_0.fix_titles
|
||||
erpnext.patches.v16_0.co_by_product_patch
|
||||
erpnext.patches.v16_0.update_requested_qty_packed_item
|
||||
erpnext.patches.v16_0.remove_payables_receivables_workspace
|
||||
@@ -479,4 +479,5 @@ erpnext.patches.v16_0.uom_category
|
||||
erpnext.patches.v16_0.merge_repost_settings_to_accounts_settings
|
||||
erpnext.patches.v16_0.set_root_type_in_account_categories
|
||||
erpnext.patches.v16_0.scr_inv_dimension
|
||||
erpnext.patches.v16_0.packed_item_inv_dimen
|
||||
erpnext.patches.v16_0.packed_item_inv_dimen
|
||||
erpnext.patches.v16_0.set_not_applicable_on_german_item_tax_templates
|
||||
@@ -1,15 +0,0 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
"""
|
||||
This patch corrects the titles of purchase orders that were set to
|
||||
the text string "{supplier_name}" instead of the actual supplier name.
|
||||
"""
|
||||
|
||||
purchase_order = frappe.qb.DocType("Purchase Order")
|
||||
(
|
||||
frappe.qb.update(purchase_order)
|
||||
.set(purchase_order.title, purchase_order.supplier_name)
|
||||
.where(purchase_order.title == "{supplier_name}")
|
||||
).run()
|
||||
28
erpnext/patches/v16_0/fix_titles.py
Normal file
28
erpnext/patches/v16_0/fix_titles.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
"""
|
||||
This patch corrects the titles of doctypes set to
|
||||
the text strings "{customer_name}" or "{supplier_name}"
|
||||
instead of the actual customer or supplier name.
|
||||
"""
|
||||
|
||||
customer_doctypes = ["POS Invoice", "Sales Invoice", "Quotation", "Sales Order", "Delivery Note"]
|
||||
supplier_doctypes = ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]
|
||||
|
||||
for doctype in customer_doctypes:
|
||||
customer_doctype = frappe.qb.DocType(doctype)
|
||||
(
|
||||
frappe.qb.update(customer_doctype)
|
||||
.set(customer_doctype.title, customer_doctype.customer_name)
|
||||
.where(customer_doctype.title == "{customer_name}")
|
||||
).run()
|
||||
|
||||
for doctype in supplier_doctypes:
|
||||
supplier_doctype = frappe.qb.DocType(doctype)
|
||||
(
|
||||
frappe.qb.update(supplier_doctype)
|
||||
.set(supplier_doctype.title, supplier_doctype.supplier_name)
|
||||
.where(supplier_doctype.title == "{supplier_name}")
|
||||
).run()
|
||||
@@ -0,0 +1,218 @@
|
||||
import frappe
|
||||
|
||||
# Snapshot of the relevant German defaults when this migration was written.
|
||||
# Migration patches must not read mutable setup data, otherwise future edits to
|
||||
# country_wise_tax.json would change what this patch does on sites that have not
|
||||
# run it yet.
|
||||
#
|
||||
# For numbered charts, compare account_number + root_type because Account.account_name
|
||||
# is not unique within a company.
|
||||
SKR04_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS = frozenset(
|
||||
{
|
||||
("3801", "Liability"),
|
||||
("3802", "Liability"),
|
||||
("3835", "Liability"),
|
||||
("1401", "Asset"),
|
||||
("1402", "Asset"),
|
||||
("1541", "Asset"),
|
||||
}
|
||||
)
|
||||
|
||||
SKR04_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS = frozenset(
|
||||
{
|
||||
("3806", "Liability"),
|
||||
("3804", "Liability"),
|
||||
("3837", "Liability"),
|
||||
("1406", "Asset"),
|
||||
("1404", "Asset"),
|
||||
("1540", "Asset"),
|
||||
}
|
||||
)
|
||||
|
||||
SKR03_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS = frozenset(
|
||||
{
|
||||
("1771", "Liability"),
|
||||
("1772", "Liability"),
|
||||
("1785", "Liability"),
|
||||
("1571", "Asset"),
|
||||
("1572", "Asset"),
|
||||
("1541", "Asset"),
|
||||
}
|
||||
)
|
||||
|
||||
SKR03_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS = frozenset(
|
||||
{
|
||||
("1776", "Liability"),
|
||||
("1774", "Liability"),
|
||||
("1787", "Liability"),
|
||||
("1576", "Asset"),
|
||||
("1574", "Asset"),
|
||||
("1540", "Asset"),
|
||||
}
|
||||
)
|
||||
|
||||
STANDARD_NOT_APPLICABLE_7_PERCENT_ACCOUNT_LABELS = frozenset(
|
||||
{
|
||||
("Umsatzsteuer 7 %", "Liability"),
|
||||
("Umsatzsteuer aus innergemeinschaftlichem Erwerb", "Liability"),
|
||||
("Umsatzsteuer nach § 13b UStG", "Liability"),
|
||||
("Abziehbare Vorsteuer 7 %", "Asset"),
|
||||
("Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb", "Asset"),
|
||||
("Abziehbare Vorsteuer nach § 13b UStG", "Asset"),
|
||||
}
|
||||
)
|
||||
|
||||
STANDARD_NOT_APPLICABLE_19_PERCENT_ACCOUNT_LABELS = frozenset(
|
||||
{
|
||||
("Umsatzsteuer 19 %", "Liability"),
|
||||
("Umsatzsteuer aus innergemeinschaftlichem Erwerb 19 %", "Liability"),
|
||||
("Umsatzsteuer nach § 13b UStG 19 %", "Liability"),
|
||||
("Abziehbare Vorsteuer 19 %", "Asset"),
|
||||
("Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 19 %", "Asset"),
|
||||
("Abziehbare Vorsteuer nach § 13b UStG 19 %", "Asset"),
|
||||
}
|
||||
)
|
||||
|
||||
STANDARD_WITH_NUMBERS_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS = frozenset(
|
||||
{
|
||||
("2321", "Liability"),
|
||||
("2331", "Liability"),
|
||||
("2341", "Liability"),
|
||||
("1521", "Asset"),
|
||||
("1531", "Asset"),
|
||||
("1541", "Asset"),
|
||||
}
|
||||
)
|
||||
|
||||
STANDARD_WITH_NUMBERS_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS = frozenset(
|
||||
{
|
||||
("2320", "Liability"),
|
||||
("2330", "Liability"),
|
||||
("2340", "Liability"),
|
||||
("1520", "Asset"),
|
||||
("1530", "Asset"),
|
||||
("1540", "Asset"),
|
||||
}
|
||||
)
|
||||
|
||||
GERMAN_ITEM_TAX_TEMPLATE_NOT_APPLICABLE_ACCOUNTS = {
|
||||
"SKR03 mit Kontonummern": {
|
||||
"identifier_field": "account_number",
|
||||
"templates": {
|
||||
"19 %": SKR03_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS,
|
||||
"7 %": SKR03_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS,
|
||||
"0 %": SKR03_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS
|
||||
| SKR03_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS
|
||||
| frozenset({("1588", "Asset")}),
|
||||
},
|
||||
},
|
||||
"SKR04 mit Kontonummern": {
|
||||
"identifier_field": "account_number",
|
||||
"templates": {
|
||||
"19 %": SKR04_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS,
|
||||
"7 %": SKR04_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS,
|
||||
"0 %": SKR04_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS
|
||||
| SKR04_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS
|
||||
| frozenset({("1433", "Asset")}),
|
||||
},
|
||||
},
|
||||
"Standard": {
|
||||
"identifier_field": "account_name",
|
||||
"templates": {
|
||||
"19 %": STANDARD_NOT_APPLICABLE_7_PERCENT_ACCOUNT_LABELS,
|
||||
"7 %": STANDARD_NOT_APPLICABLE_19_PERCENT_ACCOUNT_LABELS,
|
||||
"0%": STANDARD_NOT_APPLICABLE_7_PERCENT_ACCOUNT_LABELS
|
||||
| STANDARD_NOT_APPLICABLE_19_PERCENT_ACCOUNT_LABELS
|
||||
| frozenset({("Entstandene Einfuhrumsatzsteuer", "Asset")}),
|
||||
},
|
||||
},
|
||||
"Standard with Numbers": {
|
||||
"identifier_field": "account_number",
|
||||
"templates": {
|
||||
"19%": STANDARD_WITH_NUMBERS_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS,
|
||||
"7%": STANDARD_WITH_NUMBERS_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS,
|
||||
"0 %": STANDARD_WITH_NUMBERS_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS
|
||||
| STANDARD_WITH_NUMBERS_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS
|
||||
| frozenset({("1550", "Asset")}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def update_account_cache(accounts, account_cache):
|
||||
missing_accounts = set(accounts) - set(account_cache)
|
||||
if not missing_accounts:
|
||||
return
|
||||
|
||||
for account in frappe.get_all(
|
||||
"Account",
|
||||
filters={"name": ("in", tuple(sorted(missing_accounts)))},
|
||||
fields=["name", "account_name", "account_number", "root_type"],
|
||||
):
|
||||
account_cache[account.name] = account
|
||||
|
||||
|
||||
def get_account_identifier(account, identifier_field, account_cache):
|
||||
cached_account = account_cache.get(account)
|
||||
if not cached_account:
|
||||
return None
|
||||
|
||||
return cached_account.get(identifier_field), cached_account.root_type
|
||||
|
||||
|
||||
def execute():
|
||||
"""Backfill `not_applicable` on Item Tax Template Details for German companies.
|
||||
|
||||
Before the `not_applicable` flag existed, German default templates used
|
||||
`tax_rate: 0` to mean "this tax does not apply to the item" (as opposed to
|
||||
an explicit 0% rate). For each German company, this patch looks up the
|
||||
historical defaults for its Chart of Accounts and sets
|
||||
`not_applicable = 1` on detail rows that still match those defaults
|
||||
(same template title, same zero-rate tax account identifier set, flag still unset),
|
||||
leaving any user-customised rows untouched.
|
||||
"""
|
||||
companies = frappe.get_all(
|
||||
"Company",
|
||||
filters={"country": "Germany"},
|
||||
fields=["name", "chart_of_accounts"],
|
||||
)
|
||||
account_cache = {}
|
||||
|
||||
for company in companies:
|
||||
chart = GERMAN_ITEM_TAX_TEMPLATE_NOT_APPLICABLE_ACCOUNTS.get(company.chart_of_accounts)
|
||||
if not chart:
|
||||
continue
|
||||
|
||||
identifier_field = chart["identifier_field"]
|
||||
for template_title, target_accounts in chart["templates"].items():
|
||||
itt_names = frappe.get_all(
|
||||
"Item Tax Template",
|
||||
filters={"company": company.name, "title": template_title},
|
||||
pluck="name",
|
||||
)
|
||||
for itt_name in itt_names:
|
||||
zero_rate_details = frappe.get_all(
|
||||
"Item Tax Template Detail",
|
||||
filters={"parent": itt_name, "tax_rate": 0},
|
||||
fields=["name", "tax_type", "not_applicable"],
|
||||
)
|
||||
update_account_cache((d.tax_type for d in zero_rate_details), account_cache)
|
||||
zero_rate_accounts_by_detail = {
|
||||
d.name: get_account_identifier(d.tax_type, identifier_field, account_cache)
|
||||
for d in zero_rate_details
|
||||
}
|
||||
if any(identifier is None for identifier in zero_rate_accounts_by_detail.values()):
|
||||
continue
|
||||
|
||||
if set(zero_rate_accounts_by_detail.values()) != target_accounts:
|
||||
continue
|
||||
|
||||
for d in zero_rate_details:
|
||||
if not d.not_applicable:
|
||||
frappe.db.set_value(
|
||||
"Item Tax Template Detail",
|
||||
d.name,
|
||||
"not_applicable",
|
||||
1,
|
||||
update_modified=False,
|
||||
)
|
||||
@@ -363,13 +363,18 @@ class Project(Document):
|
||||
)
|
||||
|
||||
for user in self.users:
|
||||
# process only users who haven't received the welcome email yet
|
||||
if user.welcome_email_sent == 0:
|
||||
frappe.sendmail(
|
||||
user.user,
|
||||
subject=_("Project Collaboration Invitation"),
|
||||
content=content,
|
||||
)
|
||||
user.welcome_email_sent = 1
|
||||
# fetch canonical User data (enabled status + latest email)
|
||||
user_info = frappe.db.get_value("User", user.user, ["enabled", "email"], as_dict=True)
|
||||
# send email only if user is enabled and has a valid email
|
||||
if user_info and user_info.enabled and user_info.email:
|
||||
frappe.sendmail(
|
||||
recipients=[user_info.email],
|
||||
subject=_("Project Collaboration Invitation"),
|
||||
content=content,
|
||||
)
|
||||
user.welcome_email_sent = 1
|
||||
|
||||
|
||||
def get_timeline_data(doctype: str, name: str) -> dict[int, int]:
|
||||
|
||||
@@ -92,11 +92,7 @@ erpnext.buying = {
|
||||
this.frm.set_query("item_code", "items", function () {
|
||||
if (me.frm.doc.is_subcontracted) {
|
||||
var filters = { supplier: me.frm.doc.supplier };
|
||||
if (me.frm.doc.is_old_subcontracting_flow) {
|
||||
filters["is_sub_contracted_item"] = 1;
|
||||
} else {
|
||||
filters["is_stock_item"] = 0;
|
||||
}
|
||||
filters["is_stock_item"] = 0;
|
||||
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
|
||||
@@ -806,7 +806,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
item_tax_template: item.item_tax_template,
|
||||
child_doctype: item.doctype,
|
||||
child_docname: item.name,
|
||||
is_old_subcontracting_flow: me.frm.doc.is_old_subcontracting_flow,
|
||||
use_serial_batch_fields: item.use_serial_batch_fields,
|
||||
serial_and_batch_bundle: item.serial_and_batch_bundle,
|
||||
},
|
||||
@@ -870,10 +869,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
me.apply_rule_on_other_items({ key: item });
|
||||
}
|
||||
},
|
||||
() => {
|
||||
var company_currency = me.get_company_currency();
|
||||
me.update_item_grid_labels(company_currency);
|
||||
},
|
||||
]);
|
||||
}
|
||||
},
|
||||
@@ -1824,63 +1819,51 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
if (
|
||||
this._last_currency === this.frm.doc.currency &&
|
||||
this._last_price_list_currency === this.frm.doc.price_list_currency
|
||||
this._last_price_list_currency === this.frm.doc.price_list_currency &&
|
||||
this._last_party_account_currency === this.frm.doc.party_account_currency &&
|
||||
this._last_company_currency === company_currency
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._last_currency = this.frm.doc.currency;
|
||||
this._last_price_list_currency = this.frm.doc.price_list_currency;
|
||||
this._last_party_account_currency = this.frm.doc.party_account_currency;
|
||||
this._last_company_currency = company_currency;
|
||||
|
||||
this.change_form_labels(company_currency);
|
||||
this.change_grid_labels(company_currency);
|
||||
this.frm.refresh_fields();
|
||||
}
|
||||
|
||||
get_currency_label_options(company_currency) {
|
||||
return {
|
||||
currency: this.frm.doc.currency,
|
||||
"Company:company:default_currency": company_currency,
|
||||
party_account_currency: this.frm.doc.party_account_currency,
|
||||
};
|
||||
}
|
||||
|
||||
set_currency_labels_from_options(currency_options, parentfield) {
|
||||
const doctype = parentfield ? this.frm.fields_dict[parentfield].grid.doctype : this.frm.doc.doctype;
|
||||
const docfields = frappe.meta.get_docfields(doctype);
|
||||
|
||||
Object.entries(currency_options).forEach(([options, currency]) => {
|
||||
const fields = docfields
|
||||
.filter((df) => df.fieldtype === "Currency" && df.options === options)
|
||||
.map((df) => df.fieldname);
|
||||
|
||||
this.frm.set_currency_labels(fields, currency, parentfield);
|
||||
});
|
||||
}
|
||||
|
||||
change_form_labels(company_currency) {
|
||||
let me = this;
|
||||
const currency_options = this.get_currency_label_options(company_currency);
|
||||
|
||||
this.frm.set_currency_labels(
|
||||
[
|
||||
"advance_paid",
|
||||
"base_total",
|
||||
"base_net_total",
|
||||
"base_total_taxes_and_charges",
|
||||
"base_discount_amount",
|
||||
"base_taxes_and_charges_added",
|
||||
"base_taxes_and_charges_deducted",
|
||||
"total_amount_to_pay",
|
||||
"base_paid_amount",
|
||||
"base_write_off_amount",
|
||||
"base_change_amount",
|
||||
"base_operating_cost",
|
||||
"base_raw_material_cost",
|
||||
"base_total_cost",
|
||||
"base_secondary_items_cost",
|
||||
"base_totals_section",
|
||||
],
|
||||
company_currency
|
||||
);
|
||||
|
||||
this.frm.set_currency_labels(
|
||||
[
|
||||
"total",
|
||||
"net_total",
|
||||
"total_taxes_and_charges",
|
||||
"discount_amount",
|
||||
"taxes_and_charges_added",
|
||||
"taxes_and_charges_deducted",
|
||||
"tax_withholding_net_total",
|
||||
"paid_amount",
|
||||
"write_off_amount",
|
||||
"operating_cost",
|
||||
"secondary_items_cost",
|
||||
"raw_material_cost",
|
||||
"total_cost",
|
||||
"totals_section",
|
||||
],
|
||||
this.frm.doc.currency
|
||||
);
|
||||
this.set_currency_labels_from_options(currency_options);
|
||||
this.frm.set_currency_labels(["totals_section"], this.frm.doc.currency);
|
||||
this.frm.set_currency_labels(["base_totals_section"], company_currency);
|
||||
|
||||
this.frm.set_df_property(
|
||||
"conversion_rate",
|
||||
@@ -1956,23 +1939,25 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
change_grid_labels(company_currency) {
|
||||
var me = this;
|
||||
|
||||
this.update_item_grid_labels(company_currency);
|
||||
const currency_options = this.get_currency_label_options(company_currency);
|
||||
|
||||
this.toggle_item_grid_columns(company_currency);
|
||||
|
||||
if (this.frm.doc.operations && this.frm.doc.operations.length > 0) {
|
||||
this.frm.set_currency_labels(
|
||||
["operating_cost", "hour_rate"],
|
||||
this.frm.doc.currency,
|
||||
"operations"
|
||||
);
|
||||
this.frm.set_currency_labels(
|
||||
["base_operating_cost", "base_hour_rate"],
|
||||
company_currency,
|
||||
"operations"
|
||||
);
|
||||
for (const child_table of [
|
||||
"items",
|
||||
"operations",
|
||||
"secondary_items",
|
||||
"taxes",
|
||||
"advances",
|
||||
"payment_schedule",
|
||||
"sales_team",
|
||||
]) {
|
||||
if (this.frm.fields_dict[child_table]) {
|
||||
this.set_currency_labels_from_options(currency_options, child_table);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.frm.doc.operations && this.frm.doc.operations.length > 0) {
|
||||
var item_grid = this.frm.fields_dict["operations"].grid;
|
||||
$.each(["base_operating_cost", "base_hour_rate"], function (i, fname) {
|
||||
if (frappe.meta.get_docfield(item_grid.doctype, fname))
|
||||
@@ -1981,9 +1966,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
if (this.frm.doc.secondary_items && this.frm.doc.secondary_items.length > 0) {
|
||||
this.frm.set_currency_labels(["rate", "amount"], this.frm.doc.currency, "secondary_items");
|
||||
this.frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "secondary_items");
|
||||
|
||||
var item_grid = this.frm.fields_dict["secondary_items"].grid;
|
||||
$.each(["base_rate", "base_amount"], function (i, fname) {
|
||||
if (frappe.meta.get_docfield(item_grid.doctype, fname))
|
||||
@@ -1991,74 +1973,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
});
|
||||
}
|
||||
|
||||
if (this.frm.doc.taxes && this.frm.doc.taxes.length > 0) {
|
||||
this.frm.set_currency_labels(
|
||||
["tax_amount", "total", "tax_amount_after_discount"],
|
||||
this.frm.doc.currency,
|
||||
"taxes"
|
||||
);
|
||||
|
||||
this.frm.set_currency_labels(
|
||||
["base_tax_amount", "base_total", "base_tax_amount_after_discount"],
|
||||
company_currency,
|
||||
"taxes"
|
||||
);
|
||||
}
|
||||
|
||||
if (this.frm.doc.advances && this.frm.doc.advances.length > 0) {
|
||||
this.frm.set_currency_labels(
|
||||
["advance_amount", "allocated_amount"],
|
||||
this.frm.doc.party_account_currency,
|
||||
"advances"
|
||||
);
|
||||
}
|
||||
|
||||
this.update_payment_schedule_grid_labels(company_currency);
|
||||
}
|
||||
|
||||
update_item_grid_labels(company_currency) {
|
||||
this.frm.set_currency_labels(
|
||||
[
|
||||
"base_rate",
|
||||
"base_net_rate",
|
||||
"base_price_list_rate",
|
||||
"base_amount",
|
||||
"base_net_amount",
|
||||
"base_rate_with_margin",
|
||||
],
|
||||
company_currency,
|
||||
"items"
|
||||
);
|
||||
|
||||
this.frm.set_currency_labels(
|
||||
[
|
||||
"rate",
|
||||
"net_rate",
|
||||
"price_list_rate",
|
||||
"amount",
|
||||
"net_amount",
|
||||
"stock_uom_rate",
|
||||
"rate_with_margin",
|
||||
],
|
||||
this.frm.doc.currency,
|
||||
"items"
|
||||
);
|
||||
}
|
||||
|
||||
update_payment_schedule_grid_labels(company_currency) {
|
||||
const me = this;
|
||||
if (this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length > 0) {
|
||||
this.frm.set_currency_labels(
|
||||
["base_payment_amount", "base_outstanding", "base_paid_amount"],
|
||||
company_currency,
|
||||
"payment_schedule"
|
||||
);
|
||||
this.frm.set_currency_labels(
|
||||
["payment_amount", "outstanding", "paid_amount"],
|
||||
this.frm.doc.currency,
|
||||
"payment_schedule"
|
||||
);
|
||||
|
||||
var schedule_grid = this.frm.fields_dict["payment_schedule"].grid;
|
||||
$.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function (i, fname) {
|
||||
if (frappe.meta.get_docfield(schedule_grid.doctype, fname))
|
||||
|
||||
@@ -744,11 +744,7 @@ erpnext.utils.update_child_items = function (opts) {
|
||||
}
|
||||
} else if (frm.doc.doctype == "Purchase Order") {
|
||||
if (frm.doc.is_subcontracted) {
|
||||
if (frm.doc.is_old_subcontracting_flow) {
|
||||
filters = { is_sub_contracted_item: 1 };
|
||||
} else {
|
||||
filters = { is_stock_item: 0 };
|
||||
}
|
||||
filters = { is_stock_item: 0 };
|
||||
} else {
|
||||
filters = { is_purchase_item: 1 };
|
||||
}
|
||||
@@ -791,7 +787,6 @@ erpnext.utils.update_child_items = function (opts) {
|
||||
pos_profile: cint(frm.doc.is_pos) ? frm.doc.pos_profile : "",
|
||||
tax_category: frm.doc.tax_category,
|
||||
child_doctype: frm.doc.doctype + " Item",
|
||||
is_old_subcontracting_flow: frm.doc.is_old_subcontracting_flow,
|
||||
},
|
||||
},
|
||||
callback: function (r) {
|
||||
@@ -904,11 +899,7 @@ erpnext.utils.update_child_items = function (opts) {
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
["Purchase Order", "Sales Order"].includes(frm.doc.doctype) &&
|
||||
frm.doc.is_subcontracted &&
|
||||
!frm.doc.is_old_subcontracting_flow
|
||||
) {
|
||||
if (["Purchase Order", "Sales Order"].includes(frm.doc.doctype) && frm.doc.is_subcontracted) {
|
||||
fields.push(
|
||||
{
|
||||
fieldtype: "Link",
|
||||
|
||||
@@ -572,7 +572,9 @@ def _make_customer(source_name, ignore_permissions=False):
|
||||
if quotation.quotation_to == "Customer":
|
||||
return frappe.get_doc("Customer", quotation.party_name)
|
||||
elif quotation.quotation_to == "CRM Deal":
|
||||
return frappe.get_doc("Customer", {"crm_deal": quotation.party_name})
|
||||
customer_name = frappe.get_value("Customer", {"crm_deal": quotation.party_name})
|
||||
if customer_name:
|
||||
return frappe.get_doc("Customer", customer_name)
|
||||
|
||||
# Check if a Customer already exists for the Lead or Prospect.
|
||||
existing_customer = None
|
||||
|
||||
@@ -183,6 +183,61 @@ class TestQuotation(ERPNextTestSuite):
|
||||
|
||||
self.assertTrue(quotation.payment_schedule)
|
||||
|
||||
def test_terms_attachments_are_copied_to_quotation(self):
|
||||
terms = make_terms_and_conditions(copy_attachments_to_transaction=True)
|
||||
first_attachment = make_file_attachment(
|
||||
"Terms and Conditions",
|
||||
terms.name,
|
||||
content="First terms attachment",
|
||||
)
|
||||
|
||||
quotation = make_quotation(do_not_save=1)
|
||||
quotation.tc_name = terms.name
|
||||
quotation.insert()
|
||||
|
||||
self.assertEqual(get_attachment_urls("Quotation", quotation.name), {first_attachment.file_url})
|
||||
|
||||
second_attachment = make_file_attachment(
|
||||
"Terms and Conditions",
|
||||
terms.name,
|
||||
content="Second terms attachment",
|
||||
)
|
||||
quotation.valid_till = add_days(getdate(quotation.valid_till), 1)
|
||||
quotation.save()
|
||||
|
||||
quotation_attachments = get_attachment_urls("Quotation", quotation.name)
|
||||
self.assertEqual(quotation_attachments, {first_attachment.file_url})
|
||||
self.assertNotIn(second_attachment.file_url, quotation_attachments)
|
||||
|
||||
new_terms = make_terms_and_conditions(copy_attachments_to_transaction=True)
|
||||
new_terms_attachment = make_file_attachment(
|
||||
"Terms and Conditions",
|
||||
new_terms.name,
|
||||
content="Attachment from updated terms",
|
||||
)
|
||||
quotation.tc_name = new_terms.name
|
||||
quotation.valid_till = add_days(getdate(quotation.valid_till), 1)
|
||||
quotation.save()
|
||||
|
||||
self.assertEqual(
|
||||
get_attachment_urls("Quotation", quotation.name),
|
||||
{first_attachment.file_url, new_terms_attachment.file_url},
|
||||
)
|
||||
|
||||
def test_terms_attachments_are_not_copied_when_disabled(self):
|
||||
terms = make_terms_and_conditions(copy_attachments_to_transaction=False)
|
||||
make_file_attachment(
|
||||
"Terms and Conditions",
|
||||
terms.name,
|
||||
content="Terms attachment should stay on the template",
|
||||
)
|
||||
|
||||
quotation = make_quotation(do_not_save=1)
|
||||
quotation.tc_name = terms.name
|
||||
quotation.insert()
|
||||
|
||||
self.assertFalse(get_attachment_urls("Quotation", quotation.name))
|
||||
|
||||
@ERPNextTestSuite.change_settings(
|
||||
"Accounts Settings",
|
||||
{"automatically_fetch_payment_terms": 1},
|
||||
@@ -1148,6 +1203,42 @@ def get_quotation_dict(party_name=None, item_code=None):
|
||||
}
|
||||
|
||||
|
||||
def make_terms_and_conditions(copy_attachments_to_transaction=False):
|
||||
return frappe.get_doc(
|
||||
{
|
||||
"doctype": "Terms and Conditions",
|
||||
"title": f"_Test Terms and Conditions {frappe.generate_hash(length=8)}",
|
||||
"selling": 1,
|
||||
"terms": "Test terms",
|
||||
"copy_attachments_to_transaction": 1 if copy_attachments_to_transaction else 0,
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def make_file_attachment(doctype, docname, content):
|
||||
return frappe.get_doc(
|
||||
{
|
||||
"doctype": "File",
|
||||
"file_name": f"terms-attachment-{frappe.generate_hash(length=8)}.txt",
|
||||
"attached_to_doctype": doctype,
|
||||
"attached_to_name": docname,
|
||||
"content": content,
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def get_attachment_urls(doctype, docname):
|
||||
return {
|
||||
file.file_url
|
||||
for file in frappe.get_all(
|
||||
"File",
|
||||
filters={"attached_to_doctype": doctype, "attached_to_name": docname},
|
||||
fields=["file_url"],
|
||||
)
|
||||
if file.file_url
|
||||
}
|
||||
|
||||
|
||||
def make_quotation(**args):
|
||||
qo = frappe.new_doc("Quotation")
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -847,6 +847,7 @@
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Loyalty Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -1480,6 +1481,7 @@
|
||||
"fieldname": "amount_eligible_for_commission",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount Eligible for Commission",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -1763,7 +1765,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-30 12:19:27.522646",
|
||||
"modified": "2026-05-01 02:37:30.937916",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order",
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"field_order": [
|
||||
"title",
|
||||
"disabled",
|
||||
"column_break_ofhb",
|
||||
"copy_attachments_to_transaction",
|
||||
"applicable_modules_section",
|
||||
"selling",
|
||||
"buying",
|
||||
@@ -72,12 +74,22 @@
|
||||
{
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ofhb",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "copy_attachments_to_transaction",
|
||||
"fieldtype": "Check",
|
||||
"label": "Copy Attachments to Transaction"
|
||||
}
|
||||
],
|
||||
"icon": "icon-legal",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-14 18:22:49.285298",
|
||||
"modified": "2026-04-29 22:51:49.285298",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Terms and Conditions",
|
||||
|
||||
@@ -21,6 +21,7 @@ class TermsandConditions(Document):
|
||||
from frappe.types import DF
|
||||
|
||||
buying: DF.Check
|
||||
copy_attachments_to_transaction: DF.Check
|
||||
disabled: DF.Check
|
||||
selling: DF.Check
|
||||
terms: DF.TextEditor | None
|
||||
|
||||
@@ -1387,6 +1387,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1405,6 +1406,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1423,6 +1425,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1441,6 +1444,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1459,6 +1463,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1477,6 +1482,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1499,6 +1505,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1517,6 +1524,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1535,6 +1543,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1553,6 +1562,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1571,6 +1581,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1589,6 +1600,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1620,6 +1632,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1629,6 +1642,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1638,6 +1652,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1647,6 +1662,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1656,6 +1672,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1665,6 +1682,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1674,6 +1692,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1683,6 +1702,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1692,6 +1712,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1701,6 +1722,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1710,6 +1732,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1719,6 +1742,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1727,6 +1751,7 @@
|
||||
"account_number": "1433",
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
}
|
||||
]
|
||||
@@ -2150,6 +2175,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2168,6 +2194,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2186,6 +2213,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2204,6 +2232,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2222,6 +2251,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2240,6 +2270,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2262,6 +2293,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2280,6 +2312,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2298,6 +2331,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2316,6 +2350,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2334,6 +2369,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2352,6 +2388,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2383,6 +2420,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2392,6 +2430,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2401,6 +2440,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2410,6 +2450,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2419,6 +2460,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2428,6 +2470,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2437,6 +2480,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2446,6 +2490,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2455,6 +2500,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2464,6 +2510,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2473,6 +2520,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2482,6 +2530,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2490,6 +2539,7 @@
|
||||
"account_number": "1588",
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
}
|
||||
]
|
||||
@@ -2913,6 +2963,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2931,6 +2982,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2949,6 +3001,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2967,6 +3020,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2985,6 +3039,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3003,6 +3058,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3025,6 +3081,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3043,6 +3100,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3061,7 +3119,8 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"tax_rate": 19.00
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
"tax_type": {
|
||||
@@ -3079,6 +3138,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3097,6 +3157,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3115,6 +3176,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3146,6 +3208,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3155,6 +3218,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3164,6 +3228,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3173,6 +3238,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3182,6 +3248,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3191,6 +3258,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3200,6 +3268,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3209,6 +3278,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3218,6 +3288,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3227,6 +3298,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3236,6 +3308,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3245,6 +3318,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3253,6 +3327,7 @@
|
||||
"account_number": "1550",
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
}
|
||||
]
|
||||
@@ -3645,6 +3720,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3661,6 +3737,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3677,6 +3754,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3693,6 +3771,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3709,6 +3788,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3725,6 +3805,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3745,6 +3826,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3761,6 +3843,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3777,6 +3860,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3793,6 +3877,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3809,6 +3894,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3825,6 +3911,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3853,6 +3940,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3861,6 +3949,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3869,6 +3958,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3877,6 +3967,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3885,6 +3976,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3893,6 +3985,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3901,6 +3994,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3909,6 +4003,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3917,6 +4012,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3925,6 +4021,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3933,6 +4030,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3941,6 +4039,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3948,6 +4047,7 @@
|
||||
"account_name": "Entstandene Einfuhrumsatzsteuer",
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
}
|
||||
]
|
||||
|
||||
@@ -145,11 +145,7 @@ class Bin(Document):
|
||||
# reserved qty
|
||||
|
||||
subcontract_order = frappe.qb.DocType(subcontract_doctype)
|
||||
supplied_item = frappe.qb.DocType(
|
||||
"Purchase Order Item Supplied"
|
||||
if subcontract_doctype == "Purchase Order"
|
||||
else "Subcontracting Order Supplied Item"
|
||||
)
|
||||
supplied_item = frappe.qb.DocType("Subcontracting Order Supplied Item")
|
||||
|
||||
conditions = (
|
||||
(supplied_item.rm_item_code == self.item_code)
|
||||
@@ -157,11 +153,7 @@ class Bin(Document):
|
||||
& (subcontract_order.per_received < 100)
|
||||
& (supplied_item.reserve_warehouse == self.warehouse)
|
||||
& (
|
||||
(
|
||||
(subcontract_order.is_old_subcontracting_flow == 1)
|
||||
& (subcontract_order.status != "Closed")
|
||||
& (subcontract_order.docstatus == 1)
|
||||
)
|
||||
((subcontract_order.status != "Closed") & (subcontract_order.docstatus == 1))
|
||||
if subcontract_doctype == "Purchase Order"
|
||||
else (subcontract_order.docstatus == 1)
|
||||
)
|
||||
@@ -193,7 +185,6 @@ class Bin(Document):
|
||||
(
|
||||
(Coalesce(se.purchase_order, "") != "")
|
||||
& (subcontract_order.name == se.purchase_order)
|
||||
& (subcontract_order.is_old_subcontracting_flow == 1)
|
||||
& (subcontract_order.status != "Closed")
|
||||
)
|
||||
if subcontract_doctype == "Purchase Order"
|
||||
|
||||
@@ -1266,6 +1266,7 @@
|
||||
"fieldname": "amount_eligible_for_commission",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount Eligible for Commission",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -1470,7 +1471,7 @@
|
||||
"idx": 146,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-30 12:19:56.889644",
|
||||
"modified": "2026-05-01 02:37:31.430649",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note",
|
||||
|
||||
@@ -278,8 +278,6 @@ erpnext.stock.PurchaseReceiptController = class PurchaseReceiptController extend
|
||||
if (this.frm.doc.docstatus == 1 && this.frm.doc.status === "Closed" && this.frm.has_perm("submit")) {
|
||||
cur_frm.add_custom_button(__("Reopen"), this.reopen_purchase_receipt, __("Status"));
|
||||
}
|
||||
|
||||
this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_old_subcontracting_flow);
|
||||
}
|
||||
|
||||
make_purchase_invoice() {
|
||||
@@ -420,14 +418,6 @@ cur_frm.fields_dict["items"].grid.get_field("bom").get_query = function (doc, cd
|
||||
|
||||
frappe.provide("erpnext.buying");
|
||||
|
||||
frappe.ui.form.on("Purchase Receipt", "is_subcontracted", function (frm) {
|
||||
if (frm.doc.is_old_subcontracting_flow) {
|
||||
erpnext.buying.get_default_bom(frm);
|
||||
}
|
||||
|
||||
frm.toggle_reqd("supplier_warehouse", frm.doc.is_old_subcontracting_flow);
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Purchase Receipt Item", {
|
||||
item_code: function (frm, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_auto_repeat": 1,
|
||||
"allow_bulk_edit": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2026-04-06 14:10:33.384946",
|
||||
@@ -155,7 +156,6 @@
|
||||
"remarks",
|
||||
"range",
|
||||
"amended_from",
|
||||
"is_old_subcontracting_flow",
|
||||
"other_details",
|
||||
"connections_tab"
|
||||
],
|
||||
@@ -1151,14 +1151,6 @@
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_old_subcontracting_flow",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Is Old Subcontracting Flow",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "address_and_contact_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
@@ -1306,7 +1298,7 @@
|
||||
"idx": 261,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-06 14:11:29.630333",
|
||||
"modified": "2026-05-04 10:19:44.638858",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt",
|
||||
|
||||
@@ -88,7 +88,6 @@ class PurchaseReceipt(BuyingController):
|
||||
instructions: DF.SmallText | None
|
||||
inter_company_reference: DF.Link | None
|
||||
is_internal_supplier: DF.Check
|
||||
is_old_subcontracting_flow: DF.Check
|
||||
is_return: DF.Check
|
||||
is_subcontracted: DF.Check
|
||||
item_wise_tax_details: DF.Table[ItemWiseTaxDetail]
|
||||
@@ -149,6 +148,7 @@ class PurchaseReceipt(BuyingController):
|
||||
taxes_and_charges_deducted: DF.Currency
|
||||
tc_name: DF.Link | None
|
||||
terms: DF.TextEditor | None
|
||||
title: DF.Data | None
|
||||
total: DF.Currency
|
||||
total_net_weight: DF.Float
|
||||
total_qty: DF.Float
|
||||
@@ -725,6 +725,9 @@ class PurchaseReceipt(BuyingController):
|
||||
or stock_asset_rbnb
|
||||
)
|
||||
|
||||
if self.is_return and item.expense_account:
|
||||
loss_account = item.expense_account
|
||||
|
||||
cost_center = item.cost_center or frappe.get_cached_value(
|
||||
"Company", self.company, "cost_center"
|
||||
)
|
||||
|
||||
@@ -106,9 +106,6 @@
|
||||
"rejected_serial_no",
|
||||
"column_break_tolu",
|
||||
"batch_no",
|
||||
"subcontract_bom_section",
|
||||
"include_exploded_items",
|
||||
"bom",
|
||||
"item_weight_details",
|
||||
"weight_per_unit",
|
||||
"total_weight",
|
||||
@@ -635,26 +632,6 @@
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "bom",
|
||||
"fieldtype": "Link",
|
||||
"label": "BOM",
|
||||
"no_copy": 1,
|
||||
"options": "BOM",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!parent.is_old_subcontracting_flow"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.is_subcontracted",
|
||||
"fieldname": "include_exploded_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Include Exploded Items",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "billed_amt",
|
||||
"fieldtype": "Currency",
|
||||
@@ -1015,12 +992,6 @@
|
||||
"print_hide": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "subcontract_bom_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Subcontract BOM"
|
||||
},
|
||||
{
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Text",
|
||||
@@ -1149,7 +1120,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-29 16:01:34.154697",
|
||||
"modified": "2026-05-04 10:52:43.188507",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt Item",
|
||||
|
||||
@@ -28,7 +28,6 @@ class PurchaseReceiptItem(Document):
|
||||
base_rate_with_margin: DF.Currency
|
||||
batch_no: DF.Link | None
|
||||
billed_amt: DF.Currency
|
||||
bom: DF.Link | None
|
||||
brand: DF.Link | None
|
||||
conversion_factor: DF.Float
|
||||
cost_center: DF.Link | None
|
||||
@@ -41,7 +40,6 @@ class PurchaseReceiptItem(Document):
|
||||
from_warehouse: DF.Link | None
|
||||
has_item_scanned: DF.Check
|
||||
image: DF.Attach | None
|
||||
include_exploded_items: DF.Check
|
||||
is_fixed_asset: DF.Check
|
||||
is_free_item: DF.Check
|
||||
item_code: DF.Link
|
||||
|
||||
@@ -1200,16 +1200,6 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
|
||||
return erpnext.queries.item({ is_stock_item: 1 });
|
||||
};
|
||||
|
||||
this.frm.set_query("purchase_order", function () {
|
||||
return {
|
||||
filters: {
|
||||
docstatus: 1,
|
||||
is_old_subcontracting_flow: 1,
|
||||
company: me.frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
this.frm.set_query("subcontracting_order", function () {
|
||||
return {
|
||||
filters: {
|
||||
|
||||
@@ -128,7 +128,6 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
process_loss_percentage: DF.Percent
|
||||
process_loss_qty: DF.Float
|
||||
project: DF.Link | None
|
||||
purchase_order: DF.Link | None
|
||||
purchase_receipt_no: DF.Link | None
|
||||
purpose: DF.Literal[
|
||||
"Material Issue",
|
||||
@@ -173,16 +172,7 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.purchase_order:
|
||||
self.subcontract_data = frappe._dict(
|
||||
{
|
||||
"order_doctype": "Purchase Order",
|
||||
"order_field": "purchase_order",
|
||||
"rm_detail_field": "po_detail",
|
||||
"order_supplied_items_field": "Purchase Order Item Supplied",
|
||||
}
|
||||
)
|
||||
elif self.subcontracting_inward_order:
|
||||
if self.subcontracting_inward_order:
|
||||
self.subcontract_data = frappe._dict(
|
||||
{
|
||||
"order_doctype": "Subcontracting Inward Order",
|
||||
@@ -253,7 +243,6 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
self.validate_source_stock_entry()
|
||||
self.validate_bom()
|
||||
self.set_process_loss_qty()
|
||||
self.validate_purchase_order()
|
||||
self.validate_company_in_accounting_dimension()
|
||||
|
||||
if self.purpose in ("Manufacture", "Repack"):
|
||||
@@ -1696,19 +1685,6 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
item_code = d.original_item or d.item_code
|
||||
validate_bom_no(item_code, d.bom_no)
|
||||
|
||||
def validate_purchase_order(self):
|
||||
if self.purpose == "Send to Subcontractor" and self.get("purchase_order"):
|
||||
is_old_subcontracting_flow = frappe.db.get_value(
|
||||
"Purchase Order", self.purchase_order, "is_old_subcontracting_flow"
|
||||
)
|
||||
|
||||
if not is_old_subcontracting_flow:
|
||||
frappe.throw(
|
||||
_("Please select Subcontracting Order instead of Purchase Order {0}").format(
|
||||
self.purchase_order
|
||||
)
|
||||
)
|
||||
|
||||
def validate_closed_subcontracting_order(self):
|
||||
order = self.get("subcontracting_order") or self.get("subcontracting_inward_order")
|
||||
if order:
|
||||
|
||||
@@ -346,13 +346,8 @@ def validate_item_details(ctx: ItemDetailsCtx, item):
|
||||
throw(_(msg), title=_("Template Item Selected"))
|
||||
|
||||
elif ctx.doctype != "Material Request":
|
||||
if ctx.is_subcontracted:
|
||||
if ctx.is_old_subcontracting_flow:
|
||||
if item.is_sub_contracted_item != 1:
|
||||
throw(_("Item {0} must be a Sub-contracted Item").format(item.name))
|
||||
else:
|
||||
if item.is_stock_item:
|
||||
throw(_("Item {0} must be a Non-Stock Item").format(item.name))
|
||||
if ctx.is_subcontracted and item.is_stock_item:
|
||||
throw(_("Item {0} must be a Non-Stock Item").format(item.name))
|
||||
|
||||
|
||||
def get_basic_details(ctx: ItemDetailsCtx, item, overwrite_warehouse=True) -> ItemDetails:
|
||||
@@ -1134,6 +1129,7 @@ def insert_item_price(ctx: ItemDetailsCtx):
|
||||
currency=ctx.currency,
|
||||
uom=ctx.stock_uom,
|
||||
price_list=ctx.price_list,
|
||||
valid_from=transaction_date,
|
||||
)
|
||||
item_price.insert()
|
||||
frappe.msgprint(
|
||||
@@ -1156,6 +1152,7 @@ def insert_item_price(ctx: ItemDetailsCtx):
|
||||
"currency": ctx.currency,
|
||||
"price_list_rate": price_list_rate,
|
||||
"uom": ctx.stock_uom,
|
||||
"valid_from": transaction_date,
|
||||
}
|
||||
)
|
||||
item_price.insert()
|
||||
|
||||
@@ -72,6 +72,7 @@ def execute(filters=None):
|
||||
inv_dimension_wise_dict, filters, inv_dimension_key=inv_dimension_key, opening_row=opening_row
|
||||
)
|
||||
|
||||
item_wh_wise_prev_sle = {}
|
||||
for sle in sl_entries:
|
||||
item_detail = item_details[sle.item_code]
|
||||
|
||||
@@ -113,6 +114,21 @@ def execute(filters=None):
|
||||
elif sle.voucher_type == "Stock Reconciliation":
|
||||
sle["in_out_rate"] = sle.valuation_rate
|
||||
|
||||
if (
|
||||
sle.voucher_type == "Stock Reconciliation"
|
||||
and not sle.in_qty
|
||||
and not sle.out_qty
|
||||
and not sle.actual_qty
|
||||
):
|
||||
if prev_sle := item_wh_wise_prev_sle.get((sle.item_code, sle.warehouse)):
|
||||
bal_qty = prev_sle.get("qty_after_transaction", 0)
|
||||
qty = sle.qty_after_transaction - bal_qty
|
||||
if qty > 0:
|
||||
sle.in_qty = qty
|
||||
elif qty < 0:
|
||||
sle.out_qty = qty
|
||||
|
||||
item_wh_wise_prev_sle[(sle.item_code, sle.warehouse)] = sle
|
||||
data.append(sle)
|
||||
|
||||
if include_uom:
|
||||
|
||||
@@ -55,7 +55,6 @@ frappe.ui.form.on("Subcontracting Order", {
|
||||
filters: {
|
||||
docstatus: 1,
|
||||
is_subcontracted: 1,
|
||||
is_old_subcontracting_flow: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -138,9 +138,6 @@ class SubcontractingOrder(SubcontractingController):
|
||||
if not po.is_subcontracted:
|
||||
frappe.throw(_("Please select a valid Purchase Order that is configured for Subcontracting."))
|
||||
|
||||
if po.is_old_subcontracting_flow:
|
||||
frappe.throw(_("Please select a valid Purchase Order that has Service Items."))
|
||||
|
||||
if po.docstatus != 1:
|
||||
msg = f"Please submit Purchase Order {po.name} before proceeding."
|
||||
frappe.throw(_(msg))
|
||||
|
||||
@@ -1076,7 +1076,6 @@ def make_purchase_receipt(
|
||||
"subcontracting_receipt": source_doc.name,
|
||||
"supplier_warehouse": source_doc.supplier_warehouse,
|
||||
"is_subcontracted": 1,
|
||||
"is_old_subcontracting_flow": 0,
|
||||
"currency": frappe.get_cached_value("Company", target.company, "default_currency"),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -18,6 +18,14 @@ class UOMMustBeIntegerError(frappe.ValidationError):
|
||||
|
||||
|
||||
class TransactionBase(StatusUpdater):
|
||||
def on_change(self):
|
||||
# `on_change` also fires for `db_set()`, so only run during an actual insert/save.
|
||||
is_real_save = self.flags.in_insert or (self.doctype, self.name) in frappe.flags.currently_saving
|
||||
if not is_real_save:
|
||||
return
|
||||
|
||||
self.copy_terms_and_conditions_attachments()
|
||||
|
||||
def validate_posting_time(self):
|
||||
# set Edit Posting Date and Time to 1 while data import and restore
|
||||
if (frappe.flags.in_import or self.flags.from_restore) and self.posting_date:
|
||||
@@ -36,6 +44,56 @@ class TransactionBase(StatusUpdater):
|
||||
def validate_uom_is_integer(self, uom_field, qty_fields, child_dt=None):
|
||||
validate_uom_is_integer(self, uom_field, qty_fields, child_dt)
|
||||
|
||||
def copy_terms_and_conditions_attachments(self):
|
||||
if (
|
||||
not self.name
|
||||
or not self.meta.has_field("tc_name")
|
||||
or not self.tc_name
|
||||
or not self.has_value_changed("tc_name")
|
||||
):
|
||||
return
|
||||
|
||||
copy_attachments_to_transaction = frappe.db.get_value(
|
||||
"Terms and Conditions", self.tc_name, "copy_attachments_to_transaction"
|
||||
)
|
||||
if not cint(copy_attachments_to_transaction):
|
||||
return
|
||||
|
||||
source_attachments = frappe.get_all(
|
||||
"File",
|
||||
filters={
|
||||
"attached_to_doctype": "Terms and Conditions",
|
||||
"attached_to_name": self.tc_name,
|
||||
},
|
||||
fields=["name", "file_url"],
|
||||
)
|
||||
if not source_attachments:
|
||||
return
|
||||
|
||||
existing_file_urls = {
|
||||
attachment.file_url
|
||||
for attachment in frappe.get_all(
|
||||
"File",
|
||||
filters={
|
||||
"attached_to_doctype": self.doctype,
|
||||
"attached_to_name": self.name,
|
||||
},
|
||||
fields=["file_url"],
|
||||
)
|
||||
if attachment.file_url
|
||||
}
|
||||
|
||||
for source_attachment in source_attachments:
|
||||
if not source_attachment.file_url or source_attachment.file_url in existing_file_urls:
|
||||
continue
|
||||
|
||||
# Reuse the existing file metadata so the same on-disk blob is shared.
|
||||
new_attachment = frappe.get_doc("File", source_attachment.name).create_attachment_copy(
|
||||
attached_to_doctype=self.doctype,
|
||||
attached_to_name=self.name,
|
||||
)
|
||||
existing_file_urls.add(new_attachment.file_url)
|
||||
|
||||
def validate_with_previous_doc(self, ref):
|
||||
self.exclude_fields = ["conversion_factor", "uom"] if self.get("is_return") else []
|
||||
|
||||
@@ -285,7 +343,6 @@ class TransactionBase(StatusUpdater):
|
||||
"item_tax_template": item.get("item_tax_template"),
|
||||
"child_doctype": item.get("doctype"),
|
||||
"child_docname": item.get("name"),
|
||||
"is_old_subcontracting_flow": self.get("is_old_subcontracting_flow"),
|
||||
}
|
||||
),
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user