[REF][MOV] documentation apocalypse

Prior to this commit, the Odoo documentation was mainly split between
two repositories: odoo/odoo/doc and odoo/documentation-user. Some bits
of documentation were also hosted elsewhere (e.g., wiki, upgrade, ...).
This was causing several problems among which:
  - The theme, config, Makefile, and similar technical resources had to
    be duplicated. This resulted in inconsistent layout, features, and
    build environments from one documentation to another.
  - Some pages did not fit either documentation as they were relevant
    for both users and developers. Some were relevant to neither of the
    two (e.g., DB management).
  - Cross-doc references had to be absolute links and they broke often.
  - Merging large image files in the developer documentation would bloat
    the odoo/odoo repository. Some contributions had to be lightened to
    avoid merging too many images (e.g., Odoo development tutorials).
  - Long-time contributors to the user documentation were chilly about
    going through the merging process of the developer documentation
    because of the runbot, mergebot, `odoo-dev` repository, etc.
  - Some contributors would look for the developer documentation in the
    `odoo/documentation-user` repository.
  - Community issues about the user documentation were submitted on the
    `odoo/odoo` repository and vice-versa.

Merging all documentations in one repository will allow us to have one
place, one theme, one work process, and one set of tools (build
environment, ...) for all of the Odoo docs.

As this is a good opportunity to revamp the layout of the documentation,
a brand new theme replaces the old one. It features a new way to
navigate the documentation, centered on the idea of always letting the
reader know what is the context (enclosing section, child pages, page
structure ...) of the page they are reading. The previous theme would
quickly confuse readers as they navigated the documentation and followed
cross-application links.

The chance is also taken to get rid of all the technical dangling parts,
performance issues, and left-overs. Except for some page-specific JS
scripts, the Odoo theme Sphinx extension is re-written from scratch
based on the latest Sphinx release to benefit from the improvements and
ease future contributions.

task-2351938
task-2352371
task-2205684
task-2352544

Closes #945
This commit is contained in:
Antoine Vandevenne (anv)
2021-04-30 12:40:29 +02:00
committed by Antoine Vandevenne (anv)
parent eac5e9f865
commit e3fee2cf46
2471 changed files with 39885 additions and 60126 deletions
+222
View File
@@ -0,0 +1,222 @@
/* FIXME: hack to reverse hack in theme CSS */
main.index .toctree-wrapper > .row:first-child > .col-md-3 {
margin: 0 !important;
}
.doc-aside {
border: 1px solid #d5d5d5;
background-color: #f8f8f8;
padding: 0 !important;
font-size: 14px;
}
.doc-aside p{
padding: .5rem;
}
.intro-list {
margin-top: 1em;
}
.intro-list p {
margin: 0;
cursor: pointer;
}
.related {
background-color: hsl(317deg 16% 90%) !important;
transition: .3s;
}
.secondary {
background-color: hsl(180deg 67% 94%) !important;
transition: .3s;
}
.highlight-op {
background-color: hsl(317deg 16% 90%) !important;
transition: .3s;
}
label:hover,
label:hover,
.highlighter-list li:hover {
background-color: hsl(0, 0%, 94%);
cursor: pointer;
}
/*.accounts-table {
font-size: 90%;
}*/
.accounts-table dl {
margin: 0;
padding: .5rem 0;
}
.accounts-table h4, .accounts-table h5 {
font-weight: 700;
text-transform: uppercase;
padding: .5rem;
}
.accounts-table h4 {
text-align: center;
}
/* table root */
.accounts-table > div {
display: flex;
}
/* P&L & Balance Sheet columns */
.accounts-table > div > div {
flex: 1;
padding: .5rem;
}
.accounts-table > div > div:first-child {
border-right: 1px solid #d5d5d5
}
/* sections */
.accounts-table > div > div div {
display: flex;
flex-direction: column;
}
.accounts-table > div > div div > h5 {
order: 99;
border-top: 1px solid #d5d5d5;
border-bottom: 1px solid #d5d5d5;
/*text-transform: uppercase;*/
/*padding: 0.3em 0;*/
/*border-image: linear-gradient(to bottom, transparent, rgb(221, 225, 231), transparent, rgb(221, 225, 231), transparent) 5 0 10 / 3px 0 3px;*/
}
.accounts-table > div > div div div > h5 {
/* slightly smaller than normal 14px h5 size */
font-size: 12.5px;
/*border-image: linear-gradient(to bottom, transparent, rgb(221, 225, 231), transparent, rgb(221, 225, 231), transparent) 5 0 5;*/
}
.accounts-table dt {
font-weight: 700;
padding: 0 .5rem;
}
.accounts-table dt span {
font-style: italic;
}
.accounts-table dt span:last-child {
font-style: normal;
}
.values-table tr > * {
text-align: right;
}
.values-table tr > :first-child {
text-align: left;
}
/* 3-column (thing, debit, credit) tables */
/* 2nd and 3rd th & td of each row right-aligned and 1/4th width */
.d-c-table tr > :nth-child(2),
.d-c-table tr > :nth-child(3) {
width: 25%;
text-align: right;
}
@media (min-width: 992px) {
.accounts-table, .doc-aside .highlighter-target {
/*font-size: 90%;*/
/*color: #8d8d8d !important;*/
}
.doc-aside .highlighter-target th {
font-weight: 400;
/*font-size: 110%;*/
}
.accounts-table .related, .doc-aside .highlighter-target .related {
/*background-color: #eee !important;*/
color: #7A436B !important;
}
.accounts-table .secondary, .doc-aside .highlighter-target .secondary {
background-color: #eee !important;
color: #7A436B !important;
}
.chart-of-accounts .highlight-op,
.valuation-chart .highlight-op {
background-color: #030035;
}
.chart-of-accounts .highlight-op,
.valuation-chart-continental .highlight-op {
background-color: #030035;
}
.chart-of-accounts .highlight-op,
.valuation-chart-anglo-saxon .highlight-op {
background-color: #030035;
}
}
.entries-listing {
padding: .5rem;
}
.journal-entries .entries-listing p {
font-style: italic;
}
#reconciliation .reconciliation-example div.buttons {
display: flex;
justify-content: center;
margin-bottom: 1rem;
}
#reconciliation .reconciliation-example div.buttons button {
margin: 0 0.5em;
}
@keyframes reconcile {
0% {
opacity: 1;
padding-top: 5px;
padding-bottom: 5px;
background-color: transparent;
}
60% {
background-color: hsl(317deg 16% 90%);
}
80% {
opacity: 1;
padding-top: 5px;
padding-bottom: 5px;
}
100% {
opacity: 0;
padding-top: 0;
padding-bottom: 0;
display: none;
}
}
.reconcile1 .invoice1, .reconcile1 .invoice1 td {
animation: reconcile 2s;
}
.reconcile2 .invoice2, .reconcile2 .invoice2 td {
animation: reconcile 2s;
}
.invoice1.reconciled, .invoice2.reconciled {
display: none;
}
blockquote.highlights, blockquote.highlights p{
border: none;
margin-top: 1rem;
margin-bottom: 1rem;
text-align: center;
}
/*
lists of alternatives
*/
.alternatives-controls label {
display: block;
}
dl.alternatives > dt,
dl.alternatives > dd {
display: none;
}
dl.alternatives > dd {
margin-left: 0;
}
+18
View File
@@ -0,0 +1,18 @@
section#terms-and-conditions table td {
/* make sure icons and links aren't wrapped */
white-space: nowrap;
}
section#terms-and-conditions table th {
text-align: center;
}
section#terms-and-conditions table img.img-fluid {
margin: 0px;
}
section#terms-and-conditions table td:nth-child(2),
section#terms-and-conditions table th:nth-child(2) {
background-color: #f3f3f3;
}
+26
View File
@@ -0,0 +1,26 @@
@import "style.css";
p.rubric {
font-family: Lato, Arial, sans-serif;
font-weight: 500;
line-height: 1.1;
color: inherit;
margin-top: 10px;
margin-bottom: 8px;
}
.section p.rubric {
font-size: 30px;
}
.section .section p.rubric {
font-size: 24px;
}
.section .section .section p.rubric {
font-size: 18px;
}
.section .section .section .section p.rubric {
font-size: 14px;
}
.section .section .section .section .section p.rubric {
font-size: 12px;
}
@@ -0,0 +1,6 @@
External ID,Name,Parent Category/External ID
a1,Expenses,product.product_category_all
a2,Other Products,product.product_category_all
a3,Sellable Products,product.product_category_all
a4,Tables,a1
a5,Seating furniture,a2
1 External ID Name Parent Category/External ID
2 a1 Expenses product.product_category_all
3 a2 Other Products product.product_category_all
4 a3 Sellable Products product.product_category_all
5 a4 Tables a1
6 a5 Seating furniture a2
@@ -0,0 +1,6 @@
External ID,Name,Internal Reference,Category/External ID,Can be Expensed,Can be Purchased,Can be Sold,Sale Price,Cost,Supply Method,Product Type,Procurement Method
a6,Aluminum Stool,ALS,a5,False,True,True,49.00,25.00,Buy,Stockable Product,Make to Stock
a7,Chair,CHR,a5,False,True,True,89.00,40.00,Buy,Stockable Product,Make to Stock
a8,Table,TBL,a4,False,True,True,169.00,100.00,Buy,Stockable Product,Make to Stock
a9,Software Book Tutorial,SBT,a2,False,True,False,19.00,8.00,Buy,Consumable,Make to Stock
a10,Fuel,FL,a1,True,False,False,0.30,0.25,Buy,Service,Make to Stock
1 External ID Name Internal Reference Category/External ID Can be Expensed Can be Purchased Can be Sold Sale Price Cost Supply Method Product Type Procurement Method
2 a6 Aluminum Stool ALS a5 False True True 49.00 25.00 Buy Stockable Product Make to Stock
3 a7 Chair CHR a5 False True True 89.00 40.00 Buy Stockable Product Make to Stock
4 a8 Table TBL a4 False True True 169.00 100.00 Buy Stockable Product Make to Stock
5 a9 Software Book Tutorial SBT a2 False True False 19.00 8.00 Buy Consumable Make to Stock
6 a10 Fuel FL a1 True False False 0.30 0.25 Buy Service Make to Stock
@@ -0,0 +1,155 @@
--
-- PostgreSQL database dump
--
SET statement_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = off;
SET check_function_bodies = false;
SET client_min_messages = warning;
SET escape_string_warning = off;
SET search_path = public, pg_catalog;
SET default_tablespace = '';
SET default_with_oids = false;
--
-- Name: companies; Type: TABLE; Schema: public; Owner: fp; Tablespace:
--
CREATE TABLE companies (
id integer NOT NULL,
company_name character varying
);
ALTER TABLE public.companies OWNER TO fp;
--
-- Name: companies_id_seq; Type: SEQUENCE; Schema: public; Owner: fp
--
CREATE SEQUENCE companies_id_seq
START WITH 1
INCREMENT BY 1
NO MAXVALUE
NO MINVALUE
CACHE 1;
ALTER TABLE public.companies_id_seq OWNER TO fp;
--
-- Name: companies_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: fp
--
ALTER SEQUENCE companies_id_seq OWNED BY companies.id;
--
-- Name: companies_id_seq; Type: SEQUENCE SET; Schema: public; Owner: fp
--
SELECT pg_catalog.setval('companies_id_seq', 3, true);
--
-- Name: persons; Type: TABLE; Schema: public; Owner: fp; Tablespace:
--
CREATE TABLE persons (
id integer NOT NULL,
company_id integer,
person_name character varying
);
ALTER TABLE public.persons OWNER TO fp;
--
-- Name: persons_id_seq; Type: SEQUENCE; Schema: public; Owner: fp
--
CREATE SEQUENCE persons_id_seq
START WITH 1
INCREMENT BY 1
NO MAXVALUE
NO MINVALUE
CACHE 1;
ALTER TABLE public.persons_id_seq OWNER TO fp;
--
-- Name: persons_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: fp
--
ALTER SEQUENCE persons_id_seq OWNED BY persons.id;
--
-- Name: persons_id_seq; Type: SEQUENCE SET; Schema: public; Owner: fp
--
SELECT pg_catalog.setval('persons_id_seq', 4, true);
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: fp
--
ALTER TABLE ONLY companies ALTER COLUMN id SET DEFAULT nextval('companies_id_seq'::regclass);
--
-- Name: id; Type: DEFAULT; Schema: public; Owner: fp
--
ALTER TABLE ONLY persons ALTER COLUMN id SET DEFAULT nextval('persons_id_seq'::regclass);
--
-- Data for Name: companies; Type: TABLE DATA; Schema: public; Owner: fp
--
COPY companies (id, company_name) FROM stdin;
1 Bigees
2 Organi
3 Boum
\.
--
-- Data for Name: persons; Type: TABLE DATA; Schema: public; Owner: fp
--
COPY persons (id, company_id, person_name) FROM stdin;
1 1 Fabien
2 1 Laurence
3 2 Eric
4 3 Ramsy
\.
--
-- Name: companies_pkey; Type: CONSTRAINT; Schema: public; Owner: fp; Tablespace:
--
ALTER TABLE ONLY companies
ADD CONSTRAINT companies_pkey PRIMARY KEY (id);
--
-- Name: persons_company_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: fp
--
ALTER TABLE ONLY persons
ADD CONSTRAINT persons_company_id_fkey FOREIGN KEY (company_id) REFERENCES companies(id);
--
-- PostgreSQL database dump complete
--
@@ -0,0 +1,6 @@
Name,Reference,Tags,Customer,Street,City,Country
Credit & Leasing,3,Services,True,Central Avenue 814,Johannesburg,South Africa
Services & Finance,5,"Consultancy Services,IT Services",True,Grove Road 5,London,United Kingdom
Hydra Supplies,6,"Manufacturer,Retailer",True,Palm Street 9,Los Angeles,United States
Bolts & Screws,8,"Wholesaler,Components Buyer",True,Rua Américo 1000,Campinas,Brazil
National Parts & Supplies,18,"Manufacturer,Wholesaler",True,Guangdong Way 20,Shenzen,China
1 Name Reference Tags Customer Street City Country
2 Credit & Leasing 3 Services True Central Avenue 814 Johannesburg South Africa
3 Services & Finance 5 Consultancy Services,IT Services True Grove Road 5 London United Kingdom
4 Hydra Supplies 6 Manufacturer,Retailer True Palm Street 9 Los Angeles United States
5 Bolts & Screws 8 Wholesaler,Components Buyer True Rua Américo 1000 Campinas Brazil
6 National Parts & Supplies 18 Manufacturer,Wholesaler True Guangdong Way 20 Shenzen China
@@ -0,0 +1,9 @@
Name,Is a company,Related company,Address type,Customer,Supplier,Street,ZIP,City,State,Country
Aurora Shelves,1,,,1,0,25 Pacific Road,95101,San José,CA,United States
Roger Martins,0,Aurora Shelves,Invoice address,1,0,27 Pacific Road,95102,San José,CA,United States
House Sales Direct,1,,,1,0,104 Saint Mary Avenue,94059,Redwood,CA,United States
Yvan Holiday,0,House Sales Direct,Contact,1,0,104 Saint Mary Avenue,94060,Redwood,CA,United States
Jack Unsworth,0,House Sales Direct,Invoice address,1,0,227 Jackson Road,94061,Redwood,CA,United States
Michael Mason,0,,,1,0,16 5th Avenue,94104,San Francisco,CA,United States
International Wood,1,,,1,0,748 White House Boulevard,20004,Washington,DC,United States
Sharon Pecker,0,International Wood,Invoice address,1,0,755 White House Boulevard,20005,Washington,DC,United States
1 Name Is a company Related company Address type Customer Supplier Street ZIP City State Country
2 Aurora Shelves 1 1 0 25 Pacific Road 95101 San José CA United States
3 Roger Martins 0 Aurora Shelves Invoice address 1 0 27 Pacific Road 95102 San José CA United States
4 House Sales Direct 1 1 0 104 Saint Mary Avenue 94059 Redwood CA United States
5 Yvan Holiday 0 House Sales Direct Contact 1 0 104 Saint Mary Avenue 94060 Redwood CA United States
6 Jack Unsworth 0 House Sales Direct Invoice address 1 0 227 Jackson Road 94061 Redwood CA United States
7 Michael Mason 0 1 0 16 5th Avenue 94104 San Francisco CA United States
8 International Wood 1 1 0 748 White House Boulevard 20004 Washington DC United States
9 Sharon Pecker 0 International Wood Invoice address 1 0 755 White House Boulevard 20005 Washington DC United States
@@ -0,0 +1,10 @@
Order Date,Order Reference,Supplier,Destination,Pricelist,Order Lines / Product,Order Lines / Quantity
2012-12-15,PO00008,ASUSTeK,Stock,Default Purchase Pricelist,ADPT,20
,,,,,CARD,30
,,,,,C-Case,40
2012-12-15,PO00009,Axelor,Stock,Default Purchase Pricelist,CD,5
,,,,,CPUa8,15
2012-12-15,PO000010,China Export,Stock,Default Purchase Pricelist,HDD SH-1,10
,,,,,HDD SH-2,20
,,,,,LAP-CUS,35
,,,,,LAP-E5,40
1 Order Date Order Reference Supplier Destination Pricelist Order Lines / Product Order Lines / Quantity
2 2012-12-15 PO00008 ASUSTeK Stock Default Purchase Pricelist ADPT 20
3 CARD 30
4 C-Case 40
5 2012-12-15 PO00009 Axelor Stock Default Purchase Pricelist CD 5
6 CPUa8 15
7 2012-12-15 PO000010 China Export Stock Default Purchase Pricelist HDD SH-1 10
8 HDD SH-2 20
9 LAP-CUS 35
10 LAP-E5 40
@@ -0,0 +1,6 @@
"Order Reference","Supplier","Destination","Pricelist","Order Lines / Product","Order Lines / Quantity"
"PO000020","ASUSTeK","Stock","Default Purchase Pricelist","ADPT",20
,,,,"CARD",30
,,,,"C-Case",40
"PO000021","Axelor","Stock","Default Purchase Pricelist","CD",5
,,,,"CPUa8",15
1 Order Reference Supplier Destination Pricelist Order Lines / Product Order Lines / Quantity
2 PO000020 ASUSTeK Stock Default Purchase Pricelist ADPT 20
3 CARD 30
4 C-Case 40
5 PO000021 Axelor Stock Default Purchase Pricelist CD 5
6 CPUa8 15
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

+175
View File
@@ -0,0 +1,175 @@
/* global Immutable, React */
(function () {
// NOTE: used by memento.rst
'use strict';
function highlight(primary, secondary) {
return {
className: React.addons.classSet({
related: primary,
secondary: secondary
})
};
}
var AccountsTable = React.createClass({
render: function () {
return React.DOM.div(
{ style: { marginTop: '0' } },
React.DOM.div(// P&L
highlight(this.props.current === 'p-l'),
React.DOM.h4(null, "Profit & Loss"),
React.DOM.div(
null,
React.DOM.h5(
highlight(null, this.props.current === 'retained'),
"Net Profit"),
React.DOM.div(
highlight(null, this.props.current === 'gross-profit'),
React.DOM.h5(
highlight(this.props.current === 'gross-profit'),
"Gross Profit"),
React.DOM.dl(
null,
React.DOM.dt(null, "Revenue"),
React.DOM.dd(
null,
"Revenue"
),
React.DOM.dt(null, "Less ", "Costs of Revenue"),
React.DOM.dd(
null,
"Cost of Goods Sold"
)
)
),
React.DOM.div(
highlight(this.props.current === 'opex'),
React.DOM.h5(null, "Operating Income or Loss"),
React.DOM.dl(
null,
React.DOM.dt(
null,
"Less ", "Operating Expenses"),
React.DOM.dd(
null,
"R&D", React.DOM.br(),
"Sales, General & Administrative"
)
)
),
React.DOM.dl(
null,
React.DOM.dt(null, "Plus ", "Other Income"),
React.DOM.dd(
null,
"Foreign Exchange Gains", React.DOM.br(),
"Asset write-downs"
),
React.DOM.dt(
null,
"Less ", "Other Expenses"),
React.DOM.dd(
null,
"Interest on debt", React.DOM.br(),
"Depreciation"
)
)
)
),
React.DOM.div(//Balance Sheet
highlight(this.props.current === 'balance'),
React.DOM.h4(null, "Balance Sheet"),
React.DOM.div(
null,
React.DOM.h5(null, "Net Assets"),
React.DOM.div(
null,
React.DOM.h5(highlight(this.props.current === 'assets'), "Total Assets"),
React.DOM.dl(
highlight(null, this.props.current === 'assets'),
React.DOM.dt(null, "Current Assets"),
React.DOM.dd(
null,
"Cash & Bank Accounts", React.DOM.br(),
"Accounts Receivable", React.DOM.br(),
"Deferred Tax Assets"
),
React.DOM.dt(null, "Plus ", "Non-current Assets"),
React.DOM.dd(
null,
"Land & buildings", React.DOM.br(),
"Intangible Assets"
)
)
),
React.DOM.dl(
highlight(this.props.current === 'liabilities'),
React.DOM.dt(null, "Less ", "Current Liabilities"),
React.DOM.dd(
null,
"Accounts Payable", React.DOM.br(),
"Deferred Revenue", React.DOM.br(),
"Deferred Tax Liabilities"),
React.DOM.dt(null, "Less ", "Non-current liabilities"),
React.DOM.dd(
null,
"Long-term loans")
)
),
React.DOM.div(
highlight(null, this.props.current === 'equity'),
React.DOM.h5(highlight(this.props.current === 'equity'), "Total Equity"),
React.DOM.dl(
null,
React.DOM.dt(null, "Equity"),
React.DOM.dd(
null,
"Common Stock"
),
React.DOM.dt(
highlight(this.props.current === 'retained'),
"Plus ", "Retained Earnings"
)
)
)
)
);
}
});
document.addEventListener('DOMContentLoaded', function () {
var target = document.querySelector('.accounts-table');
if (!target) { return; }
function render(current) {
React.render(
React.createElement(AccountsTable, { current: current }),
target);
}
var list = document.querySelectorAll('.intro-list p');
Array.prototype.forEach.call(list, function (node) {
node.addEventListener('mouseover', function (e) {
if (!e.currentTarget.contains(e.target)) { return; }
e.currentTarget.classList.add('secondary');
render(e.currentTarget.className.split(/\s+/).reduce(function (acc, cls) {
if (acc) { return acc; }
var m = /^intro-(.*)$/.exec(cls);
return m && m[1];
}, null));
});
node.addEventListener('mouseout', function (e) {
// mouseout always precedes mousenter even when going into a
// child element. Since re-render should be pretty fast (just
// setting or unsetting a pair of classes) don't try to avoid
// any thrashing, things should be fast enough either way. If
// they're not, batch operations on requestAnimationFrame
// instead.
e.currentTarget.classList.remove('secondary');
render(null);
});
});
render(null);
});
})();
+56
View File
@@ -0,0 +1,56 @@
function findAncestor(element, name) {
name = name.toUpperCase();
while(element && element.nodeName.toUpperCase() !== name) {
element = element.parentElement;
};
return element;
}
function createAtom(val, options) {
var watchers = {};
var validator = options && options.validator || function () { return true; };
function transition(next) {
if (!validator(next)) {
var err = new Error(next + " failed validation");
err.name = "AssertionError";
throw err;
}
var prev = val;
val = next;
Object.keys(watchers).forEach(function (k) {
watchers[k](k, atom, prev, next);
});
}
var atom = {
addWatch: function (key, fn) {
watchers[key] = fn;
},
removeWatch: function (key) {
delete watchers[key];
},
swap: function (fn) {
var args = [val].concat([].slice.call(arguments, 1));
transition(fn.apply(null, args));
},
reset: function (v) {
transition(v);
},
deref: function () {
return val;
},
toString: function () {
return "Atom(" + JSON.stringify(val) + ")";
}
};
return atom;
}
+372
View File
@@ -0,0 +1,372 @@
/* global Immutable, React */
/* global createAtom */
(function () {
// NOTE: used by memento.rst
'use strict';
var data = createAtom();
function toKey(s, postfix) {
if (postfix) {
s += ' ' + postfix;
}
return s.replace(/[^0-9a-z ]/gi, '').toLowerCase().split(/\s+/).join('-');
}
var Controls = React.createClass({
render: function () {
var state = this.props.p;
return React.DOM.div(null, operations.map(function (op) {
var label = op.get('label'), operations = op.get('operations');
return React.DOM.label(
{
key: toKey(label),
style: { display: 'block' },
className: (operations === state.get('active') ? 'highlight-op' : void 0)
},
React.DOM.input({
type: 'checkbox',
checked: state.get('operations').contains(operations),
onChange: function (e) {
if (e.target.checked) {
data.swap(function (d) {
return d.set('active', operations)
.update('operations', function (ops) {
return ops.add(operations)
});
});
} else {
data.swap(function (d) {
return d.set('active', null) // keep visible in state map
.update('operations', function (ops) {
return ops.remove(operations);
})
});
}
}
}),
" ",
label
);
}));
}
});
var Chart = React.createClass({
render: function () {
var lastop = Immutable.Map(
(this.props.p.get('active') || Immutable.List()).map(function (op) {
return [op.get('account'), op.has('credit') ? 'credit' : 'debit'];
})
);
return React.DOM.div(
null,
React.DOM.table(
{ className: 'table table-sm' },
React.DOM.thead(
null,
React.DOM.tr(
null,
React.DOM.th(),
React.DOM.th({ className: 'text-right' }, "Debit"),
React.DOM.th({ className: 'text-right' }, "Credit"),
React.DOM.th({ className: 'text-right' }, "Balance"))
),
React.DOM.tbody(
null,
this.accounts().map(function (data) {
var highlight = lastop.get(data.get('code'));
return React.DOM.tr(
{ key: data.get('code') },
React.DOM.th(null,
data.get('level') ? '\u2001 ' : '',
data.get('code'), ' ', data.get('label')),
React.DOM.td({
className: React.addons.classSet({
'text-right': true,
'highlight-op': highlight === 'debit'
})
}, format(data.get('debit'))),
React.DOM.td({
className: React.addons.classSet({
'text-right': true,
'highlight-op': highlight === 'credit'
})
}, format(data.get('credit'))),
React.DOM.td(
{ className: 'text-right' },
((data.get('debit') || data.get('credit'))
? format(data.get('debit') - data.get('credit'), 0)
: '')
)
);
})
)
)
);
},
accounts: function () {
var data = this.props.p.get('operations');
var totals = data.toIndexedSeq().flatten(true).reduce(function (acc, op) {
return acc
.updateIn([op.get('account'), 'debit'], function (d) {
return (d || 0) + op.get('debit', zero)(data);
})
.updateIn([op.get('account'), 'credit'], function (c) {
return (c || 0) + op.get('credit', zero)(data);
});
}, Immutable.Map());
return accounts.map(function (account) {
// for each account, add sum
return account.merge(
account.get('accounts').map(function (code) {
return totals.get(code, NULL);
}).reduce(function (acc, it) {
return acc.mergeWith(function (a, b) { return a + b; }, it, NULL);
})
);
});
}
});
data.addWatch('chart', function (k, m, prev, next) {
React.render(
React.createElement(Controls, { p: next }),
document.getElementById('chart-controls'));
React.render(
React.createElement(Chart, { p: next }),
document.querySelector('.chart-of-accounts'));
});
document.addEventListener('DOMContentLoaded', function () {
var chart = findAncestor(document.querySelector('.chart-of-accounts'), 'section');
if (!chart) { return; }
var controls = document.createElement('div');
controls.setAttribute('id', 'chart-controls');
chart.insertBefore(controls, chart.lastElementChild);
data.reset(Immutable.Map({
// last-selected operation
active: null,
// set of all currently enabled operations
operations: Immutable.OrderedSet()
}));
});
var NULL = Immutable.Map({ debit: 0, credit: 0 });
var ASSETS = {
code: 1,
label: "Assets",
BANK: { code: 11000, label: "Cash" },
ACCOUNTS_RECEIVABLE: { code: 13100, label: "Accounts Receivable" },
STOCK: { code: 14000, label: "Inventory" },
STOCK_OUT: { code: 14600, label: "Goods Issued Not Invoiced" },
BUILDINGS: { code: 17200, label: "Buildings" },
DEPRECIATION: { code: 17800, label: "Accumulated Depreciation" },
TAXES_PAID: { code: 19000, label: "Deferred Tax Assets" }
};
var LIABILITIES = {
code: 2,
label: "Liabilities",
ACCOUNTS_PAYABLE: { code: 21000, label: "Accounts Payable" },
DEFERRED_REVENUE: { code: 22300, label: "Deferred Revenue" },
STOCK_IN: { code: 23000, label: "Goods Received Not Purchased" },
TAXES_PAYABLE: { code: 26200, label: "Deferred Tax Liabilities" }
};
var EQUITY = {
code: 3,
label: "Equity",
CAPITAL: { code: 31000, label: "Common Stock" }
};
var REVENUE = {
code: 4,
label: "Revenue",
SALES: { code: 41000, label: "Goods" },
SALES_SERVICES: { code: 42000, label: "Services" }
};
var EXPENSES = {
code: 5,
label: "Expenses",
GOODS_SOLD: { code: 51100, label: "Cost of Goods Sold" },
DEPRECIATION: { code: 52500, label: "Other Operating Expenses" },
PRICE_DIFFERENCE: { code: 53000, label: "Price Difference" }
};
var categories = Immutable.fromJS([ASSETS, LIABILITIES, EQUITY, REVENUE, EXPENSES], function (k, v) {
return Immutable.Iterable.isIndexed(v)
? v.toList()
: v.toOrderedMap();
});
var accounts = categories.toSeq().flatMap(function (cat) {
return Immutable.Seq.of(cat.set('level', 0)).concat(cat.filter(function (v, k) {
return k.toUpperCase() === k;
}).toIndexedSeq().map(function (acc) { return acc.set('level', 1) }));
}).map(function (account) { // add accounts: Seq<AccountCode> to each account
return account.set(
'accounts',
Immutable.Seq.of(account.get('code')).concat(
account.toIndexedSeq().map(function (val) {
return Immutable.Map.isMap(val) && val.get('code');
}).filter(function (val) { return !!val; })
)
);
});
var sale = 100,
cor = 50,
cor_tax = cor * 0.09,
tax = sale * 0.09,
total = sale + tax,
refund = sale,
refund_tax = refund * 0.09,
purchase = 52,
purchase_tax = 52 * 0.09;
var operations = Immutable.fromJS([{
label: "Company Incorporation (Initial Capital $1,000)",
operations: [
{ account: ASSETS.BANK.code, debit: constant(1000) },
{ account: EQUITY.CAPITAL.code, credit: constant(1000) }
]
}, {
label: "Customer Invoice ($100 + 9% tax) & Shipping of the Goods",
operations: [
{ account: ASSETS.ACCOUNTS_RECEIVABLE.code, debit: constant(total) },
{ account: EXPENSES.GOODS_SOLD.code, debit: constant(cor) },
{ account: REVENUE.SALES.code, credit: constant(sale) },
{ account: ASSETS.STOCK_OUT.code, credit: constant(cor) },
{ account: LIABILITIES.TAXES_PAYABLE.code, credit: constant(tax) }
]
}, {
label: "Goods Shipment to Customer",
operations: [
{ account: ASSETS.STOCK_OUT.code, debit: constant(cor) },
{ account: ASSETS.STOCK.code, credit: constant(cor) }
]
}, {
id: 'refund',
label: "Customer Refund",
operations: [
{ account: REVENUE.SALES.code, debit: constant(refund) },
{ account: LIABILITIES.TAXES_PAYABLE.code, debit: constant(refund_tax) },
{ account: ASSETS.ACCOUNTS_RECEIVABLE.code, credit: constant(refund + refund_tax) }
]
}, {
label: "Customer Payment",
operations: [
{
account: ASSETS.BANK.code, debit: function (ops) {
var refund_op = operations.find(function (op) {
return op.get('id') === 'refund';
});
return ops.contains(refund_op.get('operations'))
? total - (refund + refund_tax)
: total;
}
},
{
account: ASSETS.ACCOUNTS_RECEIVABLE.code, credit: function (ops) {
var refund_op = operations.find(function (op) {
return op.get('id') === 'refund';
});
return ops.contains(refund_op.get('operations'))
? total - (refund + refund_tax)
: total;
}
}
]
}, {
label: "Vendor Goods Received (Purchase Order: $50)",
operations: [
{ account: LIABILITIES.STOCK_IN.code, credit: constant(cor) },
{ account: ASSETS.STOCK.code, debit: constant(cor) },
]
}, {
label: "Vendor Bill (Invoice: $50)",
operations: [
{ account: LIABILITIES.STOCK_IN.code, debit: constant(cor) },
{ account: ASSETS.TAXES_PAID.code, debit: constant(cor_tax) },
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(cor + cor_tax) },
]
}, {
label: "Vendor Bill (Invoice: $52 but PO $50)",
operations: [
{ account: EXPENSES.PRICE_DIFFERENCE.code, debit: constant(purchase - cor) },
{ account: LIABILITIES.STOCK_IN.code, debit: constant(cor) },
{ account: ASSETS.TAXES_PAID.code, debit: constant(purchase_tax) },
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(purchase + purchase_tax) },
]
}, {
label: "Vendor Bill Paid ($52 + 9% tax)",
operations: [
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, debit: constant(purchase + purchase_tax) },
{ account: ASSETS.BANK.code, credit: constant(purchase + purchase_tax) }
]
}, {
label: "Acquire a building (purchase contract)",
operations: [
{ account: ASSETS.BUILDINGS.code, debit: constant(3000) },
{ account: ASSETS.TAXES_PAID.code, debit: constant(300) },
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(3300) }
]
}, {
label: "Pay for building",
operations: [
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, debit: constant(3300) },
{ account: ASSETS.BANK.code, credit: constant(3300) }
]
}, {
label: "Yearly Asset Depreciation (10% per year)",
operations: [
{ account: EXPENSES.DEPRECIATION.code, debit: constant(300) },
{ account: ASSETS.DEPRECIATION.code, credit: constant(300) }
]
}, {
label: "Customer Invoice (3 years service contract, $300)",
operations: [
{ account: ASSETS.ACCOUNTS_RECEIVABLE.code, debit: constant(total * 3) },
{ account: LIABILITIES.DEFERRED_REVENUE.code, credit: constant(sale * 3) },
{ account: LIABILITIES.TAXES_PAYABLE.code, credit: constant(tax * 3) }
]
}, {
label: "Revenue Recognition (each year, including first)",
operations: [
{ account: LIABILITIES.DEFERRED_REVENUE.code, debit: constant(sale) },
{ account: REVENUE.SALES_SERVICES.code, credit: constant(sale) },
]
}, {
id: 'pay_taxes',
label: "Pay Taxes Due",
operations: [
{
account: LIABILITIES.TAXES_PAYABLE.code, debit: function (ops) {
var this_ops = operations.find(function (op) {
return op.get('id') === 'pay_taxes';
}).get('operations');
return ops.filter(function (_ops) {
return _ops !== this_ops;
}).flatten(true).filter(function (op) {
return op.get('account') === LIABILITIES.TAXES_PAYABLE.code
}).reduce(function (acc, op) {
return acc + op.get('credit', zero)(ops) - op.get('debit', zero)(ops);
}, 0);
}
},
{
account: ASSETS.BANK.code, credit: function (ops) {
return operations.find(function (op) {
return op.get('id') === 'pay_taxes';
}).getIn(['operations', 0, 'debit'])(ops);
}
}
]
}
]);
function constant(val) { return function () { return val; }; }
var zero = constant(0);
function format(val, def) {
if (!val) { return def === undefined ? '' : def; }
if (val % 1 === 0) { return val; }
return val.toFixed(2);
}
})();
+276
View File
@@ -0,0 +1,276 @@
/* global Immutable, React */
/* global createAtom */
(function () {
'use strict';
var data = createAtom();
function toKey(s, postfix) {
if (postfix) {
s += ' ' + postfix;
}
return s.replace(/[^0-9a-z ]/gi, '').toLowerCase().split(/\s+/).join('-');
}
var Controls = React.createClass({
render: function () {
var state = this.props.p;
return React.DOM.div(null, operations.map(function (op) {
var label = op.get('label'), operations = op.get('operations');
return React.DOM.label(
{
key: toKey(label),
style: { display: 'block' },
className: (operations === state.get('active') ? 'highlight-op' : void 0)
},
React.DOM.input({
type: 'checkbox',
checked: state.get('operations').contains(operations),
onChange: function (e) {
if (e.target.checked) {
data.swap(function (d) {
return d.set('active', operations)
.update('operations', function (ops) {
return ops.add(operations);
});
});
} else {
data.swap(function (d) {
return d.set('active', null) // keep visible in state map
.update('operations', function (ops) {
return ops.remove(operations);
});
});
}
}
}),
" ",
label
);
}));
}
});
var Chart = React.createClass({
render: function () {
var lastop = Immutable.Map(
(this.props.p.get('active') || Immutable.List()).map(function (op) {
return [op.get('account'), op.has('credit') ? 'credit' : 'debit'];
})
);
return React.DOM.div(
null,
React.DOM.table(
{ className: 'table table-sm' },
React.DOM.thead(
null,
React.DOM.tr(
null,
React.DOM.th(),
React.DOM.th({ className: 'text-right' }, "Debit"),
React.DOM.th({ className: 'text-right' }, "Credit"),
React.DOM.th({ className: 'text-right' }, "Balance"))
),
React.DOM.tbody(
null,
this.accounts().map(function (data) {
var highlight = lastop.get(data.get('code'));
return React.DOM.tr(
{ key: data.get('code') },
React.DOM.th(null,
data.get('level') ? '\u2001 ' : '',
data.get('code'), ' ', data.get('label')),
React.DOM.td({
className: React.addons.classSet({
'text-right': true,
'highlight-op': highlight === 'debit'
})
}, format(data.get('debit'))),
React.DOM.td({
className: React.addons.classSet({
'text-right': true,
'highlight-op': highlight === 'credit'
})
}, format(data.get('credit'))),
React.DOM.td(
{ className: 'text-right' },
((data.get('debit') || data.get('credit'))
? format(data.get('debit') - data.get('credit'), 0)
: '')
)
);
})
)
)
);
},
accounts: function () {
var data = this.props.p.get('operations');
var totals = data.toIndexedSeq().flatten(true).reduce(function (acc, op) {
return acc
.updateIn([op.get('account'), 'debit'], function (d) {
return (d || 0) + op.get('debit', zero)(data);
})
.updateIn([op.get('account'), 'credit'], function (c) {
return (c || 0) + op.get('credit', zero)(data);
});
}, Immutable.Map());
return accounts.map(function (account) {
// for each account, add sum
return account.merge(
account.get('accounts').map(function (code) {
return totals.get(code, NULL);
}).reduce(function (acc, it) {
return acc.mergeWith(function (a, b) { return a + b; }, it, NULL);
})
);
});
}
});
data.addWatch('chart', function (k, m, prev, next) {
React.render(
React.createElement(Controls, { p: next }),
document.getElementById('chart-controls'));
React.render(
React.createElement(Chart, { p: next }),
document.querySelector('.valuation-chart-anglo-saxon'));
});
document.addEventListener('DOMContentLoaded', function () {
var chart = document.querySelector('.valuation-chart-anglo-saxon');
if (!chart) { return; }
var controls = document.createElement('div');
controls.setAttribute('id', 'chart-controls');
chart.parentNode.insertBefore(controls, chart);
data.reset(Immutable.Map({
// last-selected operation
active: null,
// set of all currently enabled operations
operations: Immutable.OrderedSet()
}));
});
var NULL = Immutable.Map({ debit: 0, credit: 0 });
var ASSETS = {
code: 1,
label: "Assets",
BANK: { code: 11000, label: "Cash" },
ACCOUNTS_RECEIVABLE: { code: 13100, label: "Accounts Receivable" },
STOCK: { code: 14000, label: "Inventory" },
RAW_MATERIALS: { code: 14100, label: "Raw Materials Inventory" },
STOCK_OUT: { code: 14600, label: "Goods Issued Not Invoiced" },
TAXES_PAID: { code: 19000, label: "Deferred Tax Assets" }
};
var LIABILITIES = {
code: 2,
label: "Liabilities",
ACCOUNTS_PAYABLE: { code: 21000, label: "Accounts Payable" },
STOCK_IN: { code: 23000, label: "Goods Received Not Purchased" },
TAXES_PAYABLE: { code: 26200, label: "Deferred Tax Liabilities" }
};
var EQUITY = {
code: 3,
label: "Equity",
CAPITAL: { code: 31000, label: "Common Stock" }
};
var REVENUE = {
code: 4,
label: "Revenue",
SALES: { code: 41000, label: "Goods" },
};
var EXPENSES = {
code: 5,
label: "Expenses",
GOODS_SOLD: { code: 51100, label: "Cost of Goods Sold" },
MANUFACTURING_OVERHEAD: { code: 52000, label: "Manufacturing Overhead" },
PRICE_DIFFERENCE: { code: 53000, label: "Price Difference" }
};
var categories = Immutable.fromJS([ASSETS, LIABILITIES, EQUITY, REVENUE, EXPENSES], function (k, v) {
return Immutable.Iterable.isIndexed(v)
? v.toList()
: v.toOrderedMap();
});
var accounts = categories.toSeq().flatMap(function (cat) {
return Immutable.Seq.of(cat.set('level', 0)).concat(cat.filter(function (v, k) {
return k.toUpperCase() === k;
}).toIndexedSeq().map(function (acc) { return acc.set('level', 1) }));
}).map(function (account) { // add accounts: Seq<AccountCode> to each account
return account.set(
'accounts',
Immutable.Seq.of(account.get('code')).concat(
account.toIndexedSeq().map(function (val) {
return Immutable.Map.isMap(val) && val.get('code');
}).filter(function (val) { return !!val; })
)
);
});
var sale = 100,
cor = 50,
cor_tax = cor * 0.09,
tax = sale * 0.09,
total = sale + tax,
purchase = 52,
purchase_tax = 52 * 0.09;
var operations = Immutable.fromJS([{
label: "Vendor Bill (PO $50, Invoice $50)",
operations: [
{ account: LIABILITIES.STOCK_IN.code, debit: constant(50) },
{ account: ASSETS.TAXES_PAID.code, debit: constant(50 * 0.09) },
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(50 * 1.09) },
]
}, {
label: "Supplier Goods Reception (PO $50, Invoice $50)",
operations: [
{ account: LIABILITIES.STOCK_IN.code, credit: constant(50) },
{ account: ASSETS.STOCK.code, debit: constant(50) },
]
}, {
label: "Vendor Bill (PO $48, Invoice $50)",
operations: [
{ account: EXPENSES.PRICE_DIFFERENCE.code, debit: constant(2) },
{ account: LIABILITIES.STOCK_IN.code, debit: constant(48) },
{ account: ASSETS.TAXES_PAID.code, debit: constant(50 * 0.09) },
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(50 * 1.09) },
]
}, {
label: "Supplier Goods Reception (PO $48, Invoice $50)",
operations: [
{ account: LIABILITIES.STOCK_IN.code, credit: constant(48) },
{ account: ASSETS.STOCK.code, debit: constant(48) },
]
}, {
label: "Customer Invoice",
operations: [
{ account: ASSETS.ACCOUNTS_RECEIVABLE.code, debit: constant(total) },
{ account: EXPENSES.GOODS_SOLD.code, debit: constant(cor) },
{ account: REVENUE.SALES.code, credit: constant(sale) },
{ account: ASSETS.STOCK_OUT.code, credit: constant(cor) },
{ account: LIABILITIES.TAXES_PAYABLE.code, credit: constant(tax) }
]
}, {
label: "Customer Shipping",
operations: [
{ account: ASSETS.STOCK_OUT.code, debit: constant(cor) },
{ account: ASSETS.STOCK.code, credit: constant(cor) }
]
}, {
label: "Production Order",
operations: [
{ account: ASSETS.STOCK.code, debit: constant(50) },
{ account: EXPENSES.MANUFACTURING_OVERHEAD.code, debit: constant(2) },
{ account: ASSETS.RAW_MATERIALS.code, credit: constant(52) }
]
}]);
function constant(val) { return function () { return val; }; }
var zero = constant(0);
function format(val, def) {
if (!val) { return def === undefined ? '' : def; }
if (val % 1 === 0) { return val; }
return val.toFixed(2);
}
})();
+265
View File
@@ -0,0 +1,265 @@
/* global Immutable, React */
/* global createAtom */
(function () {
'use strict';
var data = createAtom();
function toKey(s, postfix) {
if (postfix) {
s += ' ' + postfix;
}
return s.replace(/[^0-9a-z ]/gi, '').toLowerCase().split(/\s+/).join('-');
}
var Controls = React.createClass({
render: function () {
var state = this.props.p;
return React.DOM.div(null, operations.map(function (op) {
var label = op.get('label'), operations = op.get('operations');
return React.DOM.label(
{
key: toKey(label),
style: { display: 'block' },
className: (operations === state.get('active') ? 'highlight-op' : void 0)
},
React.DOM.input({
type: 'checkbox',
checked: state.get('operations').contains(operations),
onChange: function (e) {
if (e.target.checked) {
data.swap(function (d) {
return d.set('active', operations)
.update('operations', function (ops) {
return ops.add(operations);
});
});
} else {
data.swap(function (d) {
return d.set('active', null) // keep visible in state map
.update('operations', function (ops) {
return ops.remove(operations);
})
});
}
}
}),
" ",
label
);
}));
}
});
var Chart = React.createClass({
render: function () {
var lastop = Immutable.Map(
(this.props.p.get('active') || Immutable.List()).map(function (op) {
return [op.get('account'), op.has('credit') ? 'credit' : 'debit'];
})
);
return React.DOM.div(
null,
React.DOM.table(
{ className: 'table table-sm' },
React.DOM.thead(
null,
React.DOM.tr(
null,
React.DOM.th(),
React.DOM.th({ className: 'text-right' }, "Debit"),
React.DOM.th({ className: 'text-right' }, "Credit"),
React.DOM.th({ className: 'text-right' }, "Balance"))
),
React.DOM.tbody(
null,
this.accounts().map(function (data) {
var highlight = lastop.get(data.get('code'));
return React.DOM.tr(
{ key: data.get('code') },
React.DOM.th(null,
data.get('level') ? '\u2001 ' : '',
data.get('code'), ' ', data.get('label')),
React.DOM.td({
className: React.addons.classSet({
'text-right': true,
'highlight-op': highlight === 'debit'
})
}, format(data.get('debit'))),
React.DOM.td({
className: React.addons.classSet({
'text-right': true,
'highlight-op': highlight === 'credit'
})
}, format(data.get('credit'))),
React.DOM.td(
{ className: 'text-right' },
((data.get('debit') || data.get('credit'))
? format(data.get('debit') - data.get('credit'), 0)
: '')
)
);
})
)
)
);
},
accounts: function () {
var data = this.props.p.get('operations');
var totals = data.toIndexedSeq().flatten(true).reduce(function (acc, op) {
return acc
.updateIn([op.get('account'), 'debit'], function (d) {
return (d || 0) + op.get('debit', zero)(data);
})
.updateIn([op.get('account'), 'credit'], function (c) {
return (c || 0) + op.get('credit', zero)(data);
});
}, Immutable.Map());
return accounts.map(function (account) {
// for each account, add sum
return account.merge(
account.get('accounts').map(function (code) {
return totals.get(code, NULL);
}).reduce(function (acc, it) {
return acc.mergeWith(function (a, b) { return a + b; }, it, NULL);
})
);
});
}
});
data.addWatch('chart', function (k, m, prev, next) {
React.render(
React.createElement(Controls, { p: next }),
document.getElementById('chart-controls'));
React.render(
React.createElement(Chart, { p: next }),
document.querySelector('.valuation-chart-continental'));
});
document.addEventListener('DOMContentLoaded', function () {
var chart = document.querySelector('.valuation-chart-continental');
if (!chart) { return; }
var controls = document.createElement('div');
controls.setAttribute('id', 'chart-controls');
chart.parentNode.insertBefore(controls, chart);
data.reset(Immutable.Map({
// last-selected operation
active: null,
// set of all currently enabled operations
operations: Immutable.OrderedSet()
}));
});
var NULL = Immutable.Map({ debit: 0, credit: 0 });
var ASSETS = {
code: 1,
label: "Assets",
BANK: { code: 11000, label: "Cash" },
ACCOUNTS_RECEIVABLE: { code: 13100, label: "Accounts Receivable" },
STOCK: { code: 14000, label: "Inventory" },
RAW_MATERIALS: { code: 14100, label: "Raw Materials Inventory" },
TAXES_PAID: { code: 19000, label: "Deferred Tax Assets" }
};
var LIABILITIES = {
code: 2,
label: "Liabilities",
ACCOUNTS_PAYABLE: { code: 21000, label: "Accounts Payable" },
TAXES_PAYABLE: { code: 26200, label: "Deferred Tax Liabilities" }
};
var EQUITY = {
code: 3,
label: "Equity",
CAPITAL: { code: 31000, label: "Common Stock" }
};
var REVENUE = {
code: 4,
label: "Revenue",
SALES: { code: 41000, label: "Goods" },
};
var EXPENSES = {
code: 5,
label: "Expenses",
PURCHASED_GOODS: { code: 51000, label: "Purchased Goods" },
PURCHASED_SERVICES: { code: 52000, label: "Purchased Services" },
INVENTORY_VARIATIONS: { code: 58000, label: "Inventory Variations" },
OTHER_OPERATING_EXPENSES: { code: 59000, label: "Other Operating Expenses" },
};
var categories = Immutable.fromJS([ASSETS, LIABILITIES, EQUITY, REVENUE, EXPENSES], function (k, v) {
return Immutable.Iterable.isIndexed(v)
? v.toList()
: v.toOrderedMap();
});
var accounts = categories.toSeq().flatMap(function (cat) {
return Immutable.Seq.of(cat.set('level', 0)).concat(cat.filter(function (v, k) {
return k.toUpperCase() === k;
}).toIndexedSeq().map(function (acc) { return acc.set('level', 1) }));
}).map(function (account) { // add accounts: Seq<AccountCode> to each account
return account.set(
'accounts',
Immutable.Seq.of(account.get('code')).concat(
account.toIndexedSeq().map(function (val) {
return Immutable.Map.isMap(val) && val.get('code');
}).filter(function (val) { return !!val; })
)
);
});
var sale = 100,
cor = 50,
cor_tax = cor * 0.09,
tax = sale * 0.09,
total = sale + tax,
purchase = 52,
purchase_tax = 52 * 0.09;
var operations = Immutable.fromJS([{
label: "Vendor Invoice (PO €50, Invoice €50)",
operations: [
{ account: EXPENSES.PURCHASED_GOODS.code, debit: constant(50) },
{ account: ASSETS.TAXES_PAID.code, debit: constant(50 * 0.09) },
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(50 * 1.09) },
]
}, {
label: "Vendor Goods Reception (PO €50, Invoice €50)",
operations: [
{ account: EXPENSES.INVENTORY_VARIATIONS.code, credit: constant(50) },
{ account: ASSETS.STOCK.code, debit: constant(50) },
]
}, {
label: "Vendor Invoice (PO €48, Invoice €50)",
operations: [
{ account: EXPENSES.PURCHASED_GOODS.code, debit: constant(50) },
{ account: ASSETS.TAXES_PAID.code, debit: constant(50 * 0.09) },
{ account: LIABILITIES.ACCOUNTS_PAYABLE.code, credit: constant(50 * 1.09) },
]
}, {
label: "Vendor Goods Reception (PO €48, Invoice €50)",
operations: [
{ account: EXPENSES.INVENTORY_VARIATIONS.code, credit: constant(48) },
{ account: ASSETS.STOCK.code, debit: constant(48) },
]
}, {
label: "Customer Invoice (€100 + 9% tax)",
operations: [
{ account: ASSETS.ACCOUNTS_RECEIVABLE.code, debit: constant(total) },
{ account: REVENUE.SALES.code, credit: constant(sale) },
{ account: LIABILITIES.TAXES_PAYABLE.code, credit: constant(tax) }
]
}, {
label: "Customer Shipping",
operations: [
{ account: EXPENSES.INVENTORY_VARIATIONS.code, debit: constant(cor) },
{ account: ASSETS.STOCK.code, credit: constant(cor) }
]
}]);
function constant(val) { return function () { return val; }; }
var zero = constant(0);
function format(val, def) {
if (!val) { return def === undefined ? '' : def; }
if (val % 1 === 0) { return val; }
return val.toFixed(2);
}
})();
+297
View File
@@ -0,0 +1,297 @@
/* global Immutable, React */
/* global createAtom, findAncestor */
(function () {
'use strict';
// NOTE: memento.rst
var data = createAtom();
data.addWatch('chart', function (k, m, prev, next) {
React.render(
React.createElement(Controls, { entry: next }),
document.getElementById('entries-control'));
React.render(
React.createElement(FormatEntry, { entry: next }),
document.querySelector('.journal-entries'));
});
document.addEventListener('DOMContentLoaded', function () {
var entries_section = findAncestor(document.querySelector('.journal-entries'), 'section');
if (!entries_section) { return; }
var controls = document.createElement('div');
controls.setAttribute('id', 'entries-control');
entries_section.insertBefore(controls, entries_section.lastElementChild);
data.reset(entries.first());
});
var Controls = React.createClass({
render: function () {
var _this = this;
return React.DOM.div(
null,
entries.map(function (entry, index) {
return React.DOM.label(
{
key: index,
style: { display: 'block' },
},
React.DOM.input({
type: 'radio',
checked: Immutable.is(entry, this.props.entry),
onChange: function (e) {
data.reset(entry);
}
}),
' ',
entry.get('title')
);
}, this),
this.props.entry && React.DOM.p(null, this.props.entry.get('help'))
);
}
});
var FormatEntry = React.createClass({
render: function () {
var entry = this.props.entry;
return React.DOM.div(
null,
React.DOM.table(
{ className: 'table table-sm d-c-table' },
React.DOM.thead(
null,
React.DOM.tr(
null,
React.DOM.th(),
React.DOM.th(null, "Debit"),
React.DOM.th(null, "Credit")
)
),
React.DOM.tbody(
null,
this.render_rows()
)
),
React.createElement(Listing, {
heading: "Explanation",
items: entry && entry.get('explanation')
}),
React.createElement(Listing, {
heading: "Configuration",
items: entry && entry.get('configuration')
})
);
},
render_rows: function () {
if (!this.props.entry) { return; }
return this.props.entry.get('operations').map(this.render_row);
},
render_row: function (entry, index) {
if (!entry) {
return React.DOM.tr(
{ key: 'spacer-' + index },
React.DOM.td({ colSpan: 3 }, "\u00A0")
);
}
return React.DOM.tr(
{ key: index },
React.DOM.td(null, entry.get('account')),
React.DOM.td(null, entry.get('debit')),
React.DOM.td(null, entry.get('credit'))
);
}
});
var Listing = React.createClass({
render: function () {
if (!this.props.items || this.props.items.isEmpty()) {
return React.DOM.div();
}
var items = this.props.items, epilog = Immutable.List();
var idx = items.indexOf(null);
if (idx !== -1) {
epilog = items.slice(idx + 1);
items = items.take(idx);
}
return React.DOM.div(
{ className: 'entries-listing' },
React.DOM.h4(null, this.props.heading, ':'),
React.DOM.ul(
null,
items.map(function (item, index) {
return React.DOM.li({ key: index }, item);
})
),
epilog.map(function (item, index) {
return React.DOM.p({ key: index }, item);
})
);
}
});
var entries = Immutable.fromJS([
{
title: "Company Incorporation",
operations: [
{ account: 'Assets: Cash', debit: 1000 },
{ account: 'Equity: Common Stock', credit: 1000 }
],
explanation: [
"The company receives $1,000 in cash",
"Shares worth of $1,000 belong to the founders",
null,
"The initial capital can be cash, but could also be intellectual property, goodwill from a previous company, licences, know how, etc…",
"Sometimes, capital is not released immediately, accounts for \"capital to be released\" may be necessary."
],
configuration: []
}, {
title: "Customer Invoice ($100 + 9% tax)",
operations: [
{ account: 'Revenue: Goods', credit: 100 },
{ account: 'Liabilities: Deferred Tax Liabilities', credit: 9 },
{ account: 'Assets: Accounts Receivable', debit: 109 },
{ account: 'Assets: Inventory', credit: 50 },
{ account: 'Expenses: Cost of Goods Sold', debit: 50 }
],
explanation: [
"Revenues increase by $100",
"A tax to pay at the end of the month of $9",
"The customer owes $109",
"The inventory is decreased by $50 (shipping of the goods)",
"The cost of goods sold decreases the gross profit by $50"
],
configuration: [
"Revenue: defined on the product, or the product category if not on the product, field Income Account",
"Defered Tax Liabilities: defined on the tax used on the invoice line",
"Accounts Receivable: defined on the customer (property)",
"Inventory: defined on the category of the related product (property)",
"Expenses: defined on the product, or the category of product (property)",
null,
"The fiscal position used on the invoice may have a rule that replaces the Income Account or the tax defined on the product by another one."
]
}, {
title: "Customer payment",
operations: [
{ account: 'Assets: Cash', debit: 109 },
{ account: 'Assets: Accounts Receivable', credit: 109 }
],
explanation: [
"The company receives $109 in cash",
"The customer owes $109 less"
],
configuration: [
"Cash: defined on the journal used when registering the payment, fields Default Credit Account and Default Debit Account",
"Accounts Receivable: defined on the customer (property)"
]
}, {
title: "Supplier Bill (Purchase Order $50 but Invoice $52)",
operations: [
{ account: 'Assets: Uninvoiced Inventory', debit: 50 },
{ account: 'Assets: Deferred Tax Assets', debit: 4.68 },
{ account: 'Expenses: Price Difference', debit: 2 },
{ account: 'Liabilities: Accounts Payable', credit: 56.68 }
],
explanation: [
"A temporary account is used to note goods to receive",
"The purchase order provides prices of goods, the actual invoice may include extra costs such as shipping",
"The company still needs to pay the vendor (traded an asset against a liability)"
],
configuration: [
"Uninvoiced Inventory: defined on the product or the category of related product, field: Stock Input Account",
"Deferred Tax Assets: defined on the tax used on the purchase order line",
"Accounts Payable: defined on the supplier related to the bill",
null,
"In this scenario, the purchase order was $50 but the company received an invoice for $52 as there were extra shipping costs"
]
}, {
title: "Supplier Goods Received (Purchase Order: $50)",
operations: [
{ account: 'Assets: Inventory', debit: 50 },
{ account: 'Assets: Uninvoiced Inventory', credit: 50 },
],
explanation: [
"Inventory is increased by $50, the expected amount coming from the purchase order",
"A temporary account is used for the counterpart and will be cleared when receiving the invoice"
],
configuration: [
"Uninvoiced Inventory: defined on the product or the category of related product, field: Stock Input Account",
"Inventory: defined on the product category, field: Stock Valuation"
]
}, {
title: "Buy an asset ($300,000 - no tax)",
operations: [
{ account: 'Assets: Buildings', debit: 300000 },
{ account: 'Liabilities: Accounts Payable', credit: 300000 }
],
explanation: [
"The company gets an asset worth of $300,000",
"The company needs to pay $300,000 to the vendor (traded an asset against a liability)"
],
configuration: [
"Buildings: Defined on the Asset category selected on the supplier bill line",
"Accounts Payable: defined on the supplier related to the bill (property)"
]
}, {
title: "Pay supplier invoice",
operations: [
{ account: 'Liabilities: Accounts Payable', debit: 300000 },
{ account: 'Assets: Cash', credit: 300000 }
],
explanation: [
"The company owns $300,000 less to the supplier (liabilities are settled)",
"The company's cash is reduced by $300,000 (reduction of asset)"
],
configuration: [
"Accounts Payable: defined on the supplier you pay (property)",
"Cash: defined on the journal related to the payment method"
]
}, {
title: "Cash sale (Sales Receipt)",
operations: [
{ account: 'Assets: Cash', debit: 109 },
{ account: 'Revenue: Goods', credit: 100 },
{ account: 'Liabilities: Deferred Tax Liabilities', credit: 9 }
],
explanation: [
"Company's cash is increased by $109",
"Revenues increase by $100",
"A tax of $9 has to be paid"
],
configuration: [
"Cash: Payment method defined on the Sales Receipt",
"Sales: Defined on the product used in the sales receipt, or the category of product if empty",
"Deferred Tax Liabilities: Defined on the tax used in the sales receipt (coming from the product)"
]
}, {
title: "Customer pays invoice, 5% early payment rebate",
operations: [
{ account: 'Assets: Cash', debit: 950 },
{ account: 'Revenue: Sales Discount', debit: 50 },
{ account: 'Assets: Accounts Receivable', credit: 1000 }
],
explanation: [
"Company's cash is increased by $950",
"Sales discounts lowering effective revenues by $50",
"The customer owns $1000 less to the company"
],
configuration: [
"Cash: is defined on the journal related to the payment / bank statement",
"Sales Discount: is selected during the payment matching process",
"Accounts Receivable: is defined on the customer associated to the payment"
]
}, {
title: "Fiscal year closing — positive earnings and 50% dividends",
operations: [
{ account: 'Net Profit', debit: 1000 },
{ account: 'Equity: Retained Earnings', credit: 500 },
{ account: 'Liabilities: Dividend Payable', credit: 500 }
],
explanation: [
"The P&L is cleared (net profit)",
"50% is transferred to retained earnings",
"50% will be paid to shareholders as dividends"
],
configuration: [
"This transaction is recorded by the advisor before closing the fiscal year, depending on how the company uses its net profit."
]
}
]);
}());
+217
View File
@@ -0,0 +1,217 @@
/* global Immutable, React */
/* global createAtom */
(function () {
// NOTE: used for double_entry.rst file
'use strict';
var data = createAtom();
function toKey(s, postfix) {
if (postfix) {
s += ' ' + postfix;
}
return s.replace(/[^0-9a-z ]/gi, '').toLowerCase().split(/\s+/).join('-');
}
var Controls = React.createClass({
render: function () {
var state = this.props.p;
return React.DOM.div(null, operations.map(function (op) {
var label = op.get('label'), operations = op.get('operations');
return React.DOM.label(
{
key: toKey(label),
style: { display: 'block' },
className: (operations === state.get('active') ? 'highlight-op' : void 0)
},
React.DOM.input({
type: 'checkbox',
checked: state.get('operations').contains(operations),
onChange: function (e) {
if (e.target.checked) {
data.swap(function (d) {
return d.set('active', operations)
.update('operations', function (ops) {
return ops.add(operations);
});
});
} else {
data.swap(function (d) {
return d.set('active', null)
.update('operations', function (ops) {
return ops.remove(operations);
});
});
}
}
}),
' ',
label
);
}));
}
});
var UNIT_PRICE = 100;
function format_qty(val) {
if (val == null) { return ''; }
if (val < 0) { return val; }
return '+' + String(val);
}
function format_value(val) {
if (isNaN(val)) { return ''; }
if (val < 0) { return '-$' + String(Math.abs(val)); }
return '$' + String(val);
}
var Chart = React.createClass({
render: function () {
return React.DOM.div(
null,
React.DOM.table(
{ className: 'table table-sm' },
React.DOM.thead(
null,
React.DOM.tr(
null,
React.DOM.th(null, "Location"),
React.DOM.th({ className: 'text-right' }, "Quantity"),
React.DOM.th({ className: 'text-right' }, "Value"))
),
React.DOM.tbody(
null,
this.locations().map(function (data) {
var highlight = false;
return React.DOM.tr(
{ key: toKey(data.get('label')) },
React.DOM.th(null, data.get('level') ? '\u2001' : '', data.get('label')),
React.DOM.td({
className: React.addons.classSet({
'text-right': true,
'highlight-op': highlight
})
}, format_qty(data.get('qty'))),
React.DOM.td({
className: React.addons.classSet({
'text-right': true,
'highlight-op': highlight
})
}, format_value(data.get('qty') * UNIT_PRICE))
);
})
)
)
);
},
locations: function () {
var data = this.props.p.get('operations');
// {location: total_qty}
var totals = data.toIndexedSeq().flatten(true).reduce(function (acc, op) {
return acc.update(op.get('location'), function (qty) {
return (qty || 0) + op.get('qty');
});
}, Immutable.Map());
return locations.valueSeq().flatMap(function (loc) {
var sub_locations = loc.get('locations').valueSeq().map(function (subloc) {
return subloc.set('level', 1).set('qty', totals.get(subloc));
});
return Immutable.Seq.of(loc.set('level', 0)).concat(sub_locations);
});
}
});
data.addWatch('chart', function (k, m, prev, next) {
React.render(
React.createElement(Controls, { p: next }),
document.getElementById('chart-of-locations-controls'));
React.render(
React.createElement(Chart, { p: next }),
document.getElementById('chart-of-locations'));
});
document.addEventListener('DOMContentLoaded', function () {
var chart = document.querySelector('.chart-of-locations');
if (!chart) { return; }
chart.setAttribute('id', 'chart-of-locations');
var controls = document.createElement('div');
controls.setAttribute('id', 'chart-of-locations-controls');
chart.parentNode.insertBefore(controls, chart);
data.reset(Immutable.Map({
active: null,
operations: Immutable.OrderedSet()
}));
});
var locations = Immutable.fromJS({
warehouse: {
label: "Warehouse",
locations: {
zone1: { label: "Zone 1" },
zone2: { label: "Zone 2" }
}
},
partners: {
label: "Partner Locations",
locations: {
customers: { label: "Customers" },
suppliers: { label: "Suppliers" }
}
},
virtual: {
label: "Virtual Locations",
locations: {
initial: { label: "Initial Inventory" },
loss: { label: "Inventory Loss" },
scrap: { label: "Scrapped" },
manufacturing: { label: "Manufacturing" }
}
}
}, function (k, v) {
return Immutable.Iterable.isIndexed(v)
? v.toList()
: v.toOrderedMap();
});
var operations = Immutable.fromJS([{
label: "Initial Inventory",
operations: [
{ location: locations.getIn(['virtual', 'locations', 'initial']), qty: -3 },
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: +3 }
]
}, {
label: "Reception",
operations: [
{ location: locations.getIn(['partners', 'locations', 'suppliers']), qty: -2 },
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: +2 }
]
}, {
label: "Delivery",
operations: [
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: -1 },
{ location: locations.getIn(['partners', 'locations', 'customers']), qty: +1 }
]
}, {
label: "Return",
operations: [
{ location: locations.getIn(['partners', 'locations', 'customers']), qty: -1 },
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: +1 }
]
}, {
label: "1 product broken in Zone 1",
operations: [
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: -1 },
{ location: locations.getIn(['virtual', 'locations', 'scrap']), qty: +1 }
]
}, {
label: "Inventory check of Zone 1",
operations: [
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: -1 },
{ location: locations.getIn(['virtual', 'locations', 'loss']), qty: +1 }
]
}, {
label: "Move from Zone 1 to Zone 2",
operations: [
{ location: locations.getIn(['warehouse', 'locations', 'zone1']), qty: -1 },
{ location: locations.getIn(['warehouse', 'locations', 'zone2']), qty: +1 }
]
}]);
})();
+101
View File
@@ -0,0 +1,101 @@
(function () {
document.addEventListener('DOMContentLoaded', function () {
alternatives();
highlight();
checks_handling();
});
function highlight() {
// NOTE: used by double-entry.rst
$('.highlighter-list').each(function () {
var $this = $(this),
$target = $($this.data('target'));
$this.on('mouseout', 'li', function (e) {
$(e.currentTarget).removeClass('secondary');
$target.find('.related').removeClass('related');
}).on('mouseover', 'li', function (e) {
if (!e.currentTarget.contains(e.target)) { return; }
var $li = $(e.currentTarget);
// console.log($li, $li.data('highlight'), $target.find($li.data('highlight')));
$li.addClass('secondary');
$target.find($li.data('highlight')).addClass('related');
});
});
}
/** alternatives display:
* - prepend control for each <dt>
* - radio input with link to following dd
* - label is <dt> content
* - hide all first-level dt and dd (CSS)
* - on change
* - hide all dds
* - show dd corresponding to the selected radio
* - automatically select first control on startup
*/
function alternatives() {
// NOTE: used by double-entry.rst & valuation_methods pages
$('dl.alternatives').each(function (index) {
var $list = $(this),
$contents = $list.children('dd');
var $controls = $('<div class="alternatives-controls">').append(
$list.children('dt').map(function () {
var label = document.createElement('label'),
input = document.createElement('input');
input.setAttribute('type', 'radio');
input.setAttribute('name', 'alternatives-' + index);
var sibling = this;
while ((sibling = sibling.nextSibling) && sibling.nodeType !== 1);
input.content = sibling;
label.appendChild(input);
label.appendChild(document.createTextNode(' '));
label.appendChild(document.createTextNode(this.textContent));
return label;
}))
.insertBefore($list)
.on('change', 'input', function (e) {
// change event triggers only on newly selected input, not
// on the one being deselected
$contents.css('display', '');
var content = e.target.content;
content && (content.style.display = 'block');
})
.find('input:first').click();
});
}
function checks_handling() {
// NOTE: used by memento.rst
var $section = $('.checks-handling');
if (!$section.length) { return; }
var $ul = $section.find('ul')
.find('li').each(function () {
var txt = this.textContent;
while (this.firstChild) {
this.removeChild(this.firstChild)
}
$('<label style="display: block;">')
.append('<input type="radio" name="checks-handling">')
.append(' ')
.append(txt)
.appendTo(this);
}).end()
.on('change', 'input', update);
update();
function update() {
var $inputs = $ul.find('input');
var idx = 0;
$inputs.each(function (index) {
if (this.checked) {
idx = index;
}
}).eq(idx).prop('checked', true);
$ul.nextAll('div').hide().eq(idx).show();
}
}
})();
+72
View File
@@ -0,0 +1,72 @@
(function () {
// NOTE: memento.rst
document.addEventListener('DOMContentLoaded', function () {
var $rec = $('#reconciliation .reconciliation-example');
if (!$rec.length) { return; }
var state = 0;
var operations = [
function reconcile2() {
$rec.addClass('reconcile2');
return 1;
},
function reconcile1() {
$rec.addClass('reconcile1');
return 2;
},
function unreconcile() {
$rec.removeClass('reconcile1 reconcile2');
$1.add($2).removeClass('reconciled');
setTimeout(update_btn, 0);
return 0;
}
];
var $buttons = $('<div class="buttons">').on('click', 'button', function () {
this.disabled = true;
state = operations[state]();
}).appendTo($rec);
var $1 = $rec.find('td:contains("Invoice 1"), td:contains("Payment 1")')
.parent()
.addClass('invoice1');
var $2 = $rec.find('td:contains("Invoice 2"), td:contains("Payment 2")')
.parent()
.addClass('invoice2');
// will be called multiple times (each tr + each td), only take trs in
// account
$rec.on('animationend webkitAnimationEnd MSAnimationEnd', function (e) {
switch (e.originalEvent.target) {
case $1[0]:
$1.addClass('reconciled');
break;
case $2[0]:
$2.addClass('reconciled');
break;
}
update_btn();
});
update_btn();
function update_btn() {
var $reconcile = $('<button class="btn btn-success" type="button">')
.text("Next Reconcile")
.appendTo($buttons.empty())
switch (state) {
case 0:
$reconcile.text("Reconcile");
break;
case 1:
break;
case 2:
$reconcile.prop('disabled', true);
$('<button class="btn btn-primary" type="button">')
.text("Unreconcile")
.appendTo($buttons);
break;
default:
throw new Error("Unknown button state " + state);
}
}
});
})();
+130
View File
@@ -0,0 +1,130 @@
\usepackage{droid}
%\usepackage[default]{lato}
\usepackage{inconsolata}
\usepackage{fancyhdr}
\usepackage{graphicx}
% fancy titlepage
\usepackage{amsmath}
\usepackage{tikz}
\usepackage{epigraph}
\renewcommand\epigraphflush{flushright}
\renewcommand\epigraphsize{\normalsize}
\setlength\epigraphwidth{0.7\textwidth}
\definecolor{titlepagecolor}{rgb}{.63,.28,.54}
\DeclareFixedFont{\titlefont}{T1}{ppl}{b}{it}{0.5in}
\newcommand\titlepagedecoration{%
\begin{tikzpicture}[remember picture,overlay,shorten >= -10pt]
\coordinate (aux1) at ([yshift=-15pt]current page.north east);
\coordinate (aux2) at ([yshift=-410pt]current page.north east);
\coordinate (aux3) at ([xshift=-4.5cm]current page.north east);
\coordinate (aux4) at ([yshift=-150pt]current page.north east);
\coordinate (aux5) at ([yshift=-355pt]current page.north west);
\begin{scope}[titlepagecolor!40,line width=12pt,rounded corners=12pt, opacity=0.2]
\draw
(aux1) -- coordinate (a)
++(225:5) --
++(-45:5.1) coordinate (b);
\draw[shorten <= -10pt]
(aux3) --
(a) --
(aux1);
\draw[opacity=0.6,titlepagecolor,shorten <= -10pt]
(b) --
++(225:2.2) --
++(-45:2.2);
\end{scope}
\draw[titlepagecolor,line width=30pt,rounded corners=8pt,shorten <= -10pt]
(aux5) --
++(15,0);
\draw[titlepagecolor,line width=8pt,rounded corners=8pt,shorten <= -10pt]
(aux4) --
++(225:0.8) --
++(-45:0.8);
\begin{scope}[titlepagecolor!70,line width=6pt,rounded corners=8pt]
\draw[shorten <= -10pt]
(aux2) --
++(225:3) coordinate[pos=0.45] (c) --
++(-45:3.1);
\draw
(aux2) --
(c) --
++(135:2.5) --
++(45:2.5) --
++(-45:2.5) coordinate[pos=0.3] (d);
\draw
(d) -- +(45:1);
\end{scope}
\end{tikzpicture}%
}
% No automatic numbering, dual HTML/PDF output requires hardcoded ones
\setcounter{secnumdepth}{0}
% Max latex toc depth
\setcounter{tocdepth}{2}
\makeatletter
\setlength{\headheight}{15.2pt}
\pagestyle{fancy}
\fancyhf{}
\fancyfoot[L]{{\py@HeaderFamily\thepage}}
\fancyhead[R]{{\py@HeaderFamily \@title}}
\renewcommand{\headrulewidth}{0.1pt}
\renewcommand{\footrulewidth}{0.1pt}
\renewcommand{\maketitle}{%
\vspace*{-2.5cm}
\begin{flushright}
\sphinxlogo
\py@HeaderFamily
{\huge \@title }\par
\end{flushright}
\@thanks
\setcounter{footnote}{0}
\let\thanks\relax\let\maketitle\relax
%\gdef\@thanks{}\gdef\@author{}\gdef\@title{}
}
% For sphinx 1.8.3+, the command is \sphinxmaketitle and the
% layout is slightly different on the titlepage
\providecommand\sphinxmaketitle{}
\renewcommand{\sphinxmaketitle}{%
\vspace*{-2.5cm}
\begin{flushright}
\sphinxlogo
\py@HeaderFamily
{\hspace{-0.5cm} \huge \@title }\par
\end{flushright}
\@thanks
\setcounter{footnote}{0}
\let\thanks\relax\let\maketitle\relax
}
\newcommand{\makeodootitleold}{%
\begin{titlepage}
\titlepagedecoration
\begin{center}
\vspace{3cm}
\sphinxlogo
\end{center}
\vspace{2cm}
{ \bf \color{white} \Large \@title }
%\fdsfamily
\end{titlepage}
\newpage
}
\makeatother